maestrostack 0.1.0 → 0.1.1
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/LICENSE +21 -0
- package/README.md +38 -1
- package/dist/cli.js +132 -34
- package/dist/cli.js.map +1 -1
- package/package.json +17 -2
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Rohan Immanuel
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -111,6 +111,7 @@ run:
|
|
|
111
111
|
|---|---|---|
|
|
112
112
|
| `upload` | `path` | Uploads a local `.apk` (Android) / `.ipa` (iOS). |
|
|
113
113
|
| `app_url` | `appUrl` | Reuses an existing `bs://` app reference. |
|
|
114
|
+
| `custom_id` | `customId` | References the latest app uploaded with that custom id; no upload. |
|
|
114
115
|
|
|
115
116
|
### Execute modes
|
|
116
117
|
|
|
@@ -119,6 +120,39 @@ run:
|
|
|
119
120
|
- **`main`**: omit `run.execute` and provide a `main.yaml` at the suite root.
|
|
120
121
|
BrowserStack runs it as the entrypoint.
|
|
121
122
|
|
|
123
|
+
### Parallel execution
|
|
124
|
+
|
|
125
|
+
Set `run.maxParallel` to cap how many of your **listed devices** run at once.
|
|
126
|
+
MaestroStack splits `run.devices` into sequential batches of at most `maxParallel`
|
|
127
|
+
devices, submits one build per batch, and **waits for each build to finish before
|
|
128
|
+
starting the next**:
|
|
129
|
+
|
|
130
|
+
```yaml
|
|
131
|
+
run:
|
|
132
|
+
maxParallel: 2
|
|
133
|
+
devices:
|
|
134
|
+
- Samsung Galaxy S20-10.0
|
|
135
|
+
- Google Pixel 7-13.0
|
|
136
|
+
- iPhone 15-17.0
|
|
137
|
+
- OnePlus 9-11.0
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
With four devices and `maxParallel: 2`, that runs two builds of two devices each,
|
|
141
|
+
one after the other.
|
|
142
|
+
|
|
143
|
+
- Every device runs the full suite; nothing is assigned randomly.
|
|
144
|
+
- BrowserStack has no native per-build concurrency cap, so MaestroStack enforces
|
|
145
|
+
it by submitting batched builds sequentially (polling each build's status until
|
|
146
|
+
it completes).
|
|
147
|
+
- How many sessions can actually run at once still depends on your BrowserStack
|
|
148
|
+
plan's parallel limit. `maxParallel` lets you cap concurrency at or below that;
|
|
149
|
+
if a batch is bigger than your available parallels, BrowserStack queues the
|
|
150
|
+
extra sessions until a slot frees up.
|
|
151
|
+
- Because it waits for completion, a batched `run` is long-running (it blocks
|
|
152
|
+
until every batch finishes). Without `maxParallel`, `run` submits a single
|
|
153
|
+
build and returns immediately as before.
|
|
154
|
+
- Override per run with `--max-parallel <n>`.
|
|
155
|
+
|
|
122
156
|
### Authentication
|
|
123
157
|
|
|
124
158
|
Credentials are read from environment variables referenced as `${VAR}` in the
|
|
@@ -154,6 +188,7 @@ Secrets are never printed.
|
|
|
154
188
|
BrowserStack payload + endpoint, without making any API calls.
|
|
155
189
|
- `--device <name>` - override `run.devices` (repeatable).
|
|
156
190
|
- `--execute <path>` - override `run.execute` (repeatable; forces explicit mode).
|
|
191
|
+
- `--max-parallel <n>` - override `run.maxParallel` (run at most N listed devices at once).
|
|
157
192
|
|
|
158
193
|
```bash
|
|
159
194
|
maestrostack run -c maestrostack.android.smoke.yml
|
|
@@ -170,7 +205,8 @@ maestrostack run --execute smoke/login.yml --dry-run
|
|
|
170
205
|
3. Zips the suite under a `Flows/` prefix preserving folder structure.
|
|
171
206
|
4. Resolves the app (uploads the local binary or uses an existing `bs://` URL).
|
|
172
207
|
5. Uploads the suite zip.
|
|
173
|
-
6. POSTs the build to `.../maestro/v2/{android|ios}/build` and prints the build id
|
|
208
|
+
6. POSTs the build to `.../maestro/v2/{android|ios}/build` and prints the build id
|
|
209
|
+
plus its BrowserStack dashboard URL.
|
|
174
210
|
|
|
175
211
|
Example output:
|
|
176
212
|
|
|
@@ -186,6 +222,7 @@ Devices:
|
|
|
186
222
|
App: bs://...
|
|
187
223
|
Test suite: bs://...
|
|
188
224
|
Build ID: 5c5ab4338cec13aeb78f7a6977344556ac00bccd6
|
|
225
|
+
Build URL: https://app-automate.browserstack.com/dashboard/v2/builds/5c5ab4338cec13aeb78f7a6977344556ac00bccd6
|
|
189
226
|
```
|
|
190
227
|
|
|
191
228
|
## Development
|
package/dist/cli.js
CHANGED
|
@@ -216,6 +216,7 @@ var runSchema = z.object({
|
|
|
216
216
|
devices: z.array(nonEmpty("device")).min(1, "run.devices must contain at least one device"),
|
|
217
217
|
executeMode: z.enum(["explicit", "main"]).default("explicit"),
|
|
218
218
|
execute: z.array(nonEmpty("run.execute entry")).optional(),
|
|
219
|
+
maxParallel: z.number({ invalid_type_error: "run.maxParallel must be a number" }).int("run.maxParallel must be an integer").positive("run.maxParallel must be a positive integer").optional(),
|
|
219
220
|
options: optionsSchema
|
|
220
221
|
});
|
|
221
222
|
var configSchema = z.object({
|
|
@@ -392,12 +393,6 @@ var PLATFORM_EXT = {
|
|
|
392
393
|
};
|
|
393
394
|
async function validateApp(config, cwd) {
|
|
394
395
|
const { app, platform } = config;
|
|
395
|
-
if (app.source === "custom_id") {
|
|
396
|
-
throw new MaestroStackError(
|
|
397
|
-
'app.source "custom_id" is not supported in the MVP.',
|
|
398
|
-
["Use source: upload (with a path) or source: app_url (with a bs:// url)."]
|
|
399
|
-
);
|
|
400
|
-
}
|
|
401
396
|
if (app.source === "upload") {
|
|
402
397
|
const appPath = path8.resolve(cwd, app.path);
|
|
403
398
|
if (!await isFile(appPath)) {
|
|
@@ -444,14 +439,9 @@ async function validateCommand(opts) {
|
|
|
444
439
|
}
|
|
445
440
|
function describeApp(config) {
|
|
446
441
|
const { app } = config;
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
case "app_url":
|
|
451
|
-
return `app_url ${app.appUrl}`;
|
|
452
|
-
default:
|
|
453
|
-
return app.source;
|
|
454
|
-
}
|
|
442
|
+
if (app.source === "upload") return `upload ${app.path}`;
|
|
443
|
+
if (app.source === "app_url") return `app_url ${app.appUrl}`;
|
|
444
|
+
return `custom_id ${app.customId}`;
|
|
455
445
|
}
|
|
456
446
|
|
|
457
447
|
// src/commands/package.ts
|
|
@@ -579,13 +569,16 @@ async function uploadSuite(options) {
|
|
|
579
569
|
function buildPath(platform) {
|
|
580
570
|
return `/app-automate/maestro/v2/${platform}/build`;
|
|
581
571
|
}
|
|
582
|
-
function
|
|
572
|
+
function buildDashboardUrl(buildId) {
|
|
573
|
+
return `https://app-automate.browserstack.com/dashboard/v2/builds/${buildId}`;
|
|
574
|
+
}
|
|
575
|
+
function buildPayload(config, resolved, devices = config.run.devices) {
|
|
583
576
|
const { run } = config;
|
|
584
577
|
const payload = {
|
|
585
578
|
app: resolved.app,
|
|
586
579
|
testSuite: resolved.testSuite,
|
|
587
580
|
project: run.project,
|
|
588
|
-
devices
|
|
581
|
+
devices
|
|
589
582
|
};
|
|
590
583
|
if (run.executeMode === "explicit" && run.execute && run.execute.length > 0) {
|
|
591
584
|
payload.execute = run.execute;
|
|
@@ -607,6 +600,36 @@ async function startBuild(options) {
|
|
|
607
600
|
});
|
|
608
601
|
}
|
|
609
602
|
|
|
603
|
+
// src/browserstack/buildStatus.ts
|
|
604
|
+
var IN_PROGRESS = /* @__PURE__ */ new Set(["running", "queued", "creating"]);
|
|
605
|
+
async function getBuild(options) {
|
|
606
|
+
return bsRequest({
|
|
607
|
+
path: `/app-automate/maestro/v2/builds/${options.buildId}`,
|
|
608
|
+
method: "GET",
|
|
609
|
+
auth: options.auth
|
|
610
|
+
});
|
|
611
|
+
}
|
|
612
|
+
var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
613
|
+
async function pollBuild(options) {
|
|
614
|
+
const intervalMs = options.intervalMs ?? 2e4;
|
|
615
|
+
const timeoutMs = options.timeoutMs ?? 60 * 60 * 1e3;
|
|
616
|
+
const deadline = Date.now() + timeoutMs;
|
|
617
|
+
for (; ; ) {
|
|
618
|
+
const build = await getBuild({ auth: options.auth, buildId: options.buildId });
|
|
619
|
+
const status = build.status;
|
|
620
|
+
options.onStatus?.(status);
|
|
621
|
+
if (!IN_PROGRESS.has(status)) {
|
|
622
|
+
return status;
|
|
623
|
+
}
|
|
624
|
+
if (Date.now() >= deadline) {
|
|
625
|
+
throw new MaestroStackError(
|
|
626
|
+
`Timed out waiting for build ${options.buildId} (last status: ${status}).`
|
|
627
|
+
);
|
|
628
|
+
}
|
|
629
|
+
await sleep(intervalMs);
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
|
|
610
633
|
// src/commands/run.ts
|
|
611
634
|
var APP_PLACEHOLDER = "<resolved after upload>";
|
|
612
635
|
var SUITE_PLACEHOLDER = "<resolved after upload>";
|
|
@@ -618,15 +641,29 @@ function applyOverrides(config, opts) {
|
|
|
618
641
|
if (opts.execute && opts.execute.length > 0) {
|
|
619
642
|
run = { ...run, execute: opts.execute.map(toPosix), executeMode: "explicit" };
|
|
620
643
|
}
|
|
644
|
+
if (opts.maxParallel !== void 0 && Number.isFinite(opts.maxParallel)) {
|
|
645
|
+
run = { ...run, maxParallel: opts.maxParallel };
|
|
646
|
+
}
|
|
621
647
|
return { ...config, run };
|
|
622
648
|
}
|
|
649
|
+
function deviceBatches(devices, maxParallel) {
|
|
650
|
+
if (!maxParallel || maxParallel < 1 || maxParallel >= devices.length) {
|
|
651
|
+
return [devices];
|
|
652
|
+
}
|
|
653
|
+
const batches = [];
|
|
654
|
+
for (let i = 0; i < devices.length; i += maxParallel) {
|
|
655
|
+
batches.push(devices.slice(i, i + maxParallel));
|
|
656
|
+
}
|
|
657
|
+
return batches;
|
|
658
|
+
}
|
|
623
659
|
async function runCommand(opts) {
|
|
624
660
|
const cwd = process.cwd();
|
|
625
661
|
const loaded = await loadConfig(opts.config, cwd);
|
|
626
662
|
const config = applyOverrides(loaded.config, opts);
|
|
627
663
|
const { flows } = await validateAll({ ...loaded, config }, cwd);
|
|
664
|
+
const batches = deviceBatches(config.run.devices, config.run.maxParallel);
|
|
628
665
|
if (opts.dryRun) {
|
|
629
|
-
printDryRun(config, flows,
|
|
666
|
+
printDryRun(config, flows, batches);
|
|
630
667
|
return;
|
|
631
668
|
}
|
|
632
669
|
const { zipPath } = await createZip({
|
|
@@ -644,19 +681,47 @@ async function runCommand(opts) {
|
|
|
644
681
|
zipPath,
|
|
645
682
|
customId: config.suite.customId
|
|
646
683
|
});
|
|
647
|
-
const
|
|
648
|
-
|
|
649
|
-
testSuite:
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
684
|
+
const testSuiteUrl = suiteResult.test_suite_url;
|
|
685
|
+
if (batches.length === 1) {
|
|
686
|
+
const payload = buildPayload(config, { app: appUrl, testSuite: testSuiteUrl }, batches[0]);
|
|
687
|
+
logger.info(`Starting ${config.platform} build ...`);
|
|
688
|
+
const build = await startBuild({ auth: config.auth, platform: config.platform, payload });
|
|
689
|
+
printSingleSummary(config, appUrl, testSuiteUrl, build.build_id);
|
|
690
|
+
return;
|
|
691
|
+
}
|
|
692
|
+
logger.info(
|
|
693
|
+
`Running ${config.run.devices.length} device(s) in ${batches.length} sequential batch(es) of up to ${config.run.maxParallel}.`
|
|
694
|
+
);
|
|
695
|
+
logger.info(`App: ${appUrl}`);
|
|
696
|
+
logger.info(`Test suite: ${testSuiteUrl}`);
|
|
697
|
+
const results = [];
|
|
698
|
+
for (let i = 0; i < batches.length; i++) {
|
|
699
|
+
const batch = batches[i];
|
|
700
|
+
const label = `Batch ${i + 1}/${batches.length}`;
|
|
701
|
+
const payload = buildPayload(config, { app: appUrl, testSuite: testSuiteUrl }, batch);
|
|
702
|
+
logger.info("");
|
|
703
|
+
logger.info(`${label}: starting build on ${batch.join(", ")} ...`);
|
|
704
|
+
const build = await startBuild({ auth: config.auth, platform: config.platform, payload });
|
|
705
|
+
logger.info(`${label}: build ${build.build_id} running, waiting for it to finish ...`);
|
|
706
|
+
logger.info(`${label}: ${buildDashboardUrl(build.build_id)}`);
|
|
707
|
+
const status = await pollBuild({
|
|
708
|
+
auth: config.auth,
|
|
709
|
+
buildId: build.build_id,
|
|
710
|
+
onStatus: (s) => logger.debug(`${label} status: ${s}`)
|
|
711
|
+
});
|
|
712
|
+
logger.info(`${label}: finished with status "${status}".`);
|
|
713
|
+
results.push({ buildId: build.build_id, status, devices: batch });
|
|
714
|
+
}
|
|
715
|
+
printBatchSummary(config, appUrl, testSuiteUrl, results);
|
|
654
716
|
}
|
|
655
717
|
async function resolveApp(config, cwd) {
|
|
656
718
|
const { app } = config;
|
|
657
719
|
if (app.source === "app_url") {
|
|
658
720
|
return app.appUrl;
|
|
659
721
|
}
|
|
722
|
+
if (app.source === "custom_id") {
|
|
723
|
+
return app.customId;
|
|
724
|
+
}
|
|
660
725
|
if (app.source === "upload") {
|
|
661
726
|
logger.info(`Uploading app: ${app.path} ...`);
|
|
662
727
|
const result = await uploadApp({
|
|
@@ -666,13 +731,15 @@ async function resolveApp(config, cwd) {
|
|
|
666
731
|
});
|
|
667
732
|
return result.app_url;
|
|
668
733
|
}
|
|
669
|
-
throw new MaestroStackError(
|
|
734
|
+
throw new MaestroStackError("Unsupported app source.");
|
|
670
735
|
}
|
|
671
736
|
function previewApp(config) {
|
|
672
737
|
const { app } = config;
|
|
673
|
-
|
|
738
|
+
if (app.source === "app_url") return app.appUrl;
|
|
739
|
+
if (app.source === "custom_id") return app.customId;
|
|
740
|
+
return APP_PLACEHOLDER;
|
|
674
741
|
}
|
|
675
|
-
function printDryRun(config, flows,
|
|
742
|
+
function printDryRun(config, flows, batches) {
|
|
676
743
|
logger.info("Dry run only. No API calls made.");
|
|
677
744
|
logger.info("");
|
|
678
745
|
logger.info("Would package:");
|
|
@@ -687,19 +754,34 @@ ${app.path}`);
|
|
|
687
754
|
} else if (app.source === "app_url") {
|
|
688
755
|
logger.info(`Would use app:
|
|
689
756
|
${app.appUrl}`);
|
|
757
|
+
} else if (app.source === "custom_id") {
|
|
758
|
+
logger.info(`Would use app (custom_id):
|
|
759
|
+
${app.customId}`);
|
|
760
|
+
}
|
|
761
|
+
logger.info("");
|
|
762
|
+
if (batches.length > 1) {
|
|
763
|
+
logger.info(
|
|
764
|
+
`Would submit ${batches.length} builds sequentially (max ${config.run.maxParallel} device(s) at once), waiting for each to finish:`
|
|
765
|
+
);
|
|
766
|
+
batches.forEach((batch, i) => {
|
|
767
|
+
logger.info(` Batch ${i + 1}: ${batch.join(", ")}`);
|
|
768
|
+
});
|
|
769
|
+
} else {
|
|
770
|
+
logger.info("Would submit 1 build on all devices.");
|
|
690
771
|
}
|
|
691
772
|
logger.info("");
|
|
692
773
|
logger.info(`Would call:
|
|
693
774
|
POST ${buildPath(config.platform)}`);
|
|
694
775
|
logger.info("");
|
|
695
|
-
const payload = buildPayload(
|
|
696
|
-
|
|
697
|
-
testSuite: SUITE_PLACEHOLDER
|
|
698
|
-
|
|
699
|
-
|
|
776
|
+
const payload = buildPayload(
|
|
777
|
+
config,
|
|
778
|
+
{ app: previewApp(config), testSuite: SUITE_PLACEHOLDER },
|
|
779
|
+
batches[0]
|
|
780
|
+
);
|
|
781
|
+
logger.info(batches.length > 1 ? "Payload (batch 1):" : "Payload:");
|
|
700
782
|
logger.info(JSON.stringify(payload, null, 2));
|
|
701
783
|
}
|
|
702
|
-
function
|
|
784
|
+
function printSingleSummary(config, appUrl, testSuiteUrl, buildId) {
|
|
703
785
|
logger.info("");
|
|
704
786
|
logger.info("MaestroStack run started");
|
|
705
787
|
logger.info("");
|
|
@@ -713,6 +795,22 @@ function printSummary(config, appUrl, testSuiteUrl, buildId) {
|
|
|
713
795
|
logger.info(`App: ${appUrl}`);
|
|
714
796
|
logger.info(`Test suite: ${testSuiteUrl}`);
|
|
715
797
|
logger.info(`Build ID: ${buildId}`);
|
|
798
|
+
logger.info(`Build URL: ${buildDashboardUrl(buildId)}`);
|
|
799
|
+
}
|
|
800
|
+
function printBatchSummary(config, appUrl, testSuiteUrl, results) {
|
|
801
|
+
logger.info("");
|
|
802
|
+
logger.info("MaestroStack run complete");
|
|
803
|
+
logger.info("");
|
|
804
|
+
logger.info(`Project: ${config.run.project}`);
|
|
805
|
+
logger.info(`Platform: ${config.platform}`);
|
|
806
|
+
logger.info(`App: ${appUrl}`);
|
|
807
|
+
logger.info(`Test suite: ${testSuiteUrl}`);
|
|
808
|
+
logger.info("");
|
|
809
|
+
logger.info("Builds:");
|
|
810
|
+
for (const r of results) {
|
|
811
|
+
logger.info(`- ${r.buildId} [${r.status}] on ${r.devices.join(", ")}`);
|
|
812
|
+
logger.info(` ${buildDashboardUrl(r.buildId)}`);
|
|
813
|
+
}
|
|
716
814
|
}
|
|
717
815
|
|
|
718
816
|
// src/commands/uploadApp.ts
|
|
@@ -793,7 +891,7 @@ program.command("validate").description("Validate the config, suite structure an
|
|
|
793
891
|
program.command("package").description("Discover flows and create the suite zip without uploading").action(action(packageCommand));
|
|
794
892
|
program.command("upload-app").description("Upload only the app and print its BrowserStack app_url").action(action(uploadAppCommand));
|
|
795
893
|
program.command("upload-suite").description("Package and upload only the Maestro suite").action(action(uploadSuiteCommand));
|
|
796
|
-
program.command("run").description("Package, upload and trigger a BrowserStack Maestro build").option("--dry-run", "validate and print the payload without making API calls").option("--device <name>", "override run.devices (repeatable)", collect, []).option("--execute <path>", "override run.execute (repeatable)", collect, []).action(action(runCommand));
|
|
894
|
+
program.command("run").description("Package, upload and trigger a BrowserStack Maestro build").option("--dry-run", "validate and print the payload without making API calls").option("--device <name>", "override run.devices (repeatable)", collect, []).option("--execute <path>", "override run.execute (repeatable)", collect, []).option("--max-parallel <n>", "run at most N listed devices at once (sequential batches)", (v) => parseInt(v, 10)).action(action(runCommand));
|
|
797
895
|
program.parseAsync(process.argv).catch((err) => {
|
|
798
896
|
logger.error(err instanceof Error ? err.message : String(err));
|
|
799
897
|
process.exitCode = 1;
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cli.ts","../src/commands/init.ts","../src/utils/errors.ts","../src/utils/fs.ts","../src/utils/logger.ts","../src/commands/package.ts","../src/config/loadConfig.ts","../src/config/resolveEnv.ts","../src/config/schema.ts","../src/suite/createZip.ts","../src/utils/paths.ts","../src/commands/validate.ts","../src/suite/discoverFlows.ts","../src/suite/validateSuite.ts","../src/commands/run.ts","../src/browserstack/uploadApp.ts","../src/browserstack/client.ts","../src/browserstack/uploadSuite.ts","../src/browserstack/startBuild.ts","../src/commands/uploadApp.ts","../src/commands/uploadSuite.ts"],"sourcesContent":["import { Command } from \"commander\";\nimport { initCommand } from \"./commands/init.js\";\nimport { packageCommand } from \"./commands/package.js\";\nimport { runCommand } from \"./commands/run.js\";\nimport { uploadAppCommand } from \"./commands/uploadApp.js\";\nimport { uploadSuiteCommand } from \"./commands/uploadSuite.js\";\nimport { validateCommand } from \"./commands/validate.js\";\nimport { isMaestroStackError } from \"./utils/errors.js\";\nimport { logger, setDebug } from \"./utils/logger.js\";\n\nconst program = new Command();\n\n/** Accumulate repeated CLI options (e.g. --device a --device b) into an array. */\nfunction collect(value: string, previous: string[]): string[] {\n return [...previous, value];\n}\n\n/**\n * Wrap a command action: merge program-level (global) options, apply debug mode,\n * and turn expected errors into a clean message with exit code 1 instead of an\n * unhandled rejection / stack dump.\n */\nfunction action<T extends object>(\n fn: (opts: T & { config?: string; debug?: boolean }) => Promise<void>,\n): (opts: T) => Promise<void> {\n return async (rawOpts: T) => {\n const opts = { ...program.opts(), ...rawOpts };\n if (opts.debug) setDebug(true);\n try {\n await fn(opts);\n } catch (err) {\n if (isMaestroStackError(err)) {\n logger.error(err.message);\n for (const line of err.details ?? []) {\n logger.error(` ${line}`);\n }\n } else {\n logger.error(err instanceof Error ? (err.stack ?? err.message) : String(err));\n }\n process.exitCode = 1;\n }\n };\n}\n\nprogram\n .name(\"maestrostack\")\n .description(\"Config-driven CLI for running Maestro tests on BrowserStack App Automate.\")\n .version(\"0.1.0\")\n .option(\"-c, --config <path>\", \"path to the config file\")\n .option(\"--debug\", \"enable debug logging\");\n\nprogram\n .command(\"init\")\n .description(\"Create a starter maestrostack.yml\")\n .option(\"--android\", \"scaffold an Android config\")\n .option(\"--ios\", \"scaffold an iOS config\")\n .option(\"--force\", \"overwrite an existing config\")\n .action(action(initCommand));\n\nprogram\n .command(\"validate\")\n .description(\"Validate the config, suite structure and devices\")\n .action(action(validateCommand));\n\nprogram\n .command(\"package\")\n .description(\"Discover flows and create the suite zip without uploading\")\n .action(action(packageCommand));\n\nprogram\n .command(\"upload-app\")\n .description(\"Upload only the app and print its BrowserStack app_url\")\n .action(action(uploadAppCommand));\n\nprogram\n .command(\"upload-suite\")\n .description(\"Package and upload only the Maestro suite\")\n .action(action(uploadSuiteCommand));\n\nprogram\n .command(\"run\")\n .description(\"Package, upload and trigger a BrowserStack Maestro build\")\n .option(\"--dry-run\", \"validate and print the payload without making API calls\")\n .option(\"--device <name>\", \"override run.devices (repeatable)\", collect, [])\n .option(\"--execute <path>\", \"override run.execute (repeatable)\", collect, [])\n .action(action(runCommand));\n\nprogram.parseAsync(process.argv).catch((err) => {\n logger.error(err instanceof Error ? err.message : String(err));\n process.exitCode = 1;\n});\n","import { writeFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport type { Platform } from \"../config/schema.js\";\nimport { MaestroStackError } from \"../utils/errors.js\";\nimport { pathExists } from \"../utils/fs.js\";\nimport { logger } from \"../utils/logger.js\";\n\nexport interface InitOptions {\n android?: boolean;\n ios?: boolean;\n force?: boolean;\n}\n\nconst CONFIG_NAME = \"maestrostack.yml\";\n\n/** Create a starter maestrostack.yml. */\nexport async function initCommand(opts: InitOptions): Promise<void> {\n if (opts.android && opts.ios) {\n throw new MaestroStackError(\"Pass only one of --android or --ios.\");\n }\n const platform: Platform = opts.ios ? \"ios\" : \"android\";\n\n const cwd = process.cwd();\n const target = path.join(cwd, CONFIG_NAME);\n\n if ((await pathExists(target)) && !opts.force) {\n throw new MaestroStackError(`${CONFIG_NAME} already exists.`, [\n \"Use --force to overwrite it.\",\n ]);\n }\n\n await writeFile(target, template(platform), \"utf8\");\n logger.info(`Created ${CONFIG_NAME} (platform: ${platform}).`);\n logger.info(\"Next: set BROWSERSTACK_USERNAME / BROWSERSTACK_ACCESS_KEY, then run `maestrostack validate`.\");\n}\n\nfunction template(platform: Platform): string {\n const appPath = platform === \"ios\" ? \"./apps/app-release.ipa\" : \"./apps/app-release.apk\";\n const devices =\n platform === \"ios\"\n ? [\"iPhone 15-17.0\"]\n : [\"Samsung Galaxy S20-10.0\", \"Google Pixel 7-13.0\"];\n\n const deviceLines = devices.map((d) => ` - ${d}`).join(\"\\n\");\n\n // Always \\n line endings so the file is identical across platforms.\n return [\n \"version: 1\",\n \"\",\n \"auth:\",\n \" username: ${BROWSERSTACK_USERNAME}\",\n \" accessKey: ${BROWSERSTACK_ACCESS_KEY}\",\n \"\",\n `platform: ${platform}`,\n \"\",\n \"app:\",\n \" source: upload\",\n ` path: ${appPath}`,\n \" customId: SampleApp\",\n \"\",\n \"suite:\",\n \" root: .\",\n \" packageName: Flows.zip\",\n \" customId: SampleTest\",\n \" include:\",\n \" - smoke/**/*.yml\",\n \" - regression/**/*.yml\",\n \" exclude:\",\n \" - apps/**\",\n \"\",\n \"run:\",\n \" project: Maestro_Test\",\n \" devices:\",\n deviceLines,\n \" executeMode: explicit\",\n \" execute:\",\n \" - smoke/login.yml\",\n \" options:\",\n \" networkLogs: true\",\n \" deviceLogs: true\",\n \"\",\n ].join(\"\\n\");\n}\n","/**\n * Error type for all expected, user-facing failures (bad config, missing files,\n * API errors, etc). The CLI entrypoint catches these and prints a clean message\n * with exit code 1, rather than dumping a stack trace.\n */\nexport class MaestroStackError extends Error {\n /** Optional extra lines shown beneath the main message (e.g. hints). */\n readonly details?: string[];\n\n constructor(message: string, details?: string[]) {\n super(message);\n this.name = \"MaestroStackError\";\n this.details = details;\n }\n}\n\n/** Type guard for {@link MaestroStackError}. */\nexport function isMaestroStackError(err: unknown): err is MaestroStackError {\n return err instanceof MaestroStackError;\n}\n","import { constants } from \"node:fs\";\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\n\n/** Returns true if a file or directory exists at `p`. */\nexport async function pathExists(p: string): Promise<boolean> {\n try {\n await fs.access(p, constants.F_OK);\n return true;\n } catch {\n return false;\n }\n}\n\n/** Returns true if `p` exists and is a regular file. */\nexport async function isFile(p: string): Promise<boolean> {\n try {\n const stat = await fs.stat(p);\n return stat.isFile();\n } catch {\n return false;\n }\n}\n\n/** Returns true if `p` exists and is a directory. */\nexport async function isDirectory(p: string): Promise<boolean> {\n try {\n const stat = await fs.stat(p);\n return stat.isDirectory();\n } catch {\n return false;\n }\n}\n\n/** Creates a directory (and parents) if it does not already exist. */\nexport async function ensureDir(dir: string): Promise<void> {\n await fs.mkdir(dir, { recursive: true });\n}\n\n/** Removes a directory tree if present; no-op if missing. */\nexport async function removeDir(dir: string): Promise<void> {\n await fs.rm(dir, { recursive: true, force: true });\n}\n\n/** Lower-cased file extension including the dot (e.g. \".apk\"). */\nexport function extname(p: string): string {\n return path.extname(p).toLowerCase();\n}\n","/* eslint-disable no-console */\n\nlet debugEnabled = false;\n\n/** Enable or disable debug-level output (wired to the global --debug flag). */\nexport function setDebug(enabled: boolean): void {\n debugEnabled = enabled;\n}\n\nexport function isDebugEnabled(): boolean {\n return debugEnabled;\n}\n\nexport const logger = {\n /** Primary user-facing output. */\n info(message = \"\"): void {\n console.log(message);\n },\n warn(message: string): void {\n console.warn(`warning: ${message}`);\n },\n error(message: string): void {\n console.error(message);\n },\n /** Only printed when --debug is set. Prefixed so it is visually distinct. */\n debug(message: string): void {\n if (debugEnabled) {\n console.error(`[debug] ${message}`);\n }\n },\n};\n\n/**\n * Mask a secret for display, keeping only the last few characters so logs are\n * still useful for debugging without exposing the value. Empty/short values are\n * fully masked.\n */\nexport function redact(secret: string | undefined): string {\n if (!secret) return \"\";\n if (secret.length <= 4) return \"****\";\n return `****${secret.slice(-4)}`;\n}\n","import path from \"node:path\";\nimport { loadConfig } from \"../config/loadConfig.js\";\nimport { createZip } from \"../suite/createZip.js\";\nimport { logger } from \"../utils/logger.js\";\nimport { validateAll, type GlobalOptions } from \"./validate.js\";\n\n/**\n * Discover flows and create the suite zip without uploading. Useful for\n * debugging the resulting archive structure.\n */\nexport async function packageCommand(opts: GlobalOptions): Promise<void> {\n const cwd = process.cwd();\n const loaded = await loadConfig(opts.config, cwd);\n const { config } = loaded;\n const { flows } = await validateAll(loaded, cwd);\n\n const { zipPath } = await createZip({\n files: flows,\n suiteRoot: config.suite.root,\n packageName: config.suite.packageName,\n cwd,\n });\n\n logger.info(`Created suite zip: ${path.relative(cwd, zipPath) || zipPath}`);\n logger.info(\"Included flows:\");\n for (const flow of flows) {\n logger.info(`- ${flow}`);\n }\n}\n","import { readFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport dotenv from \"dotenv\";\nimport { parse as parseYaml } from \"yaml\";\nimport { MaestroStackError } from \"../utils/errors.js\";\nimport { isFile } from \"../utils/fs.js\";\nimport { logger } from \"../utils/logger.js\";\nimport { resolveEnv } from \"./resolveEnv.js\";\nimport { configSchema, type Config } from \"./schema.js\";\n\nconst DEFAULT_CONFIG_NAMES = [\"maestrostack.yml\", \"maestrostack.yaml\"];\n\nexport interface LoadedConfig {\n config: Config;\n /** Absolute path to the resolved config file. */\n configPath: string;\n}\n\n/**\n * Resolve which config file to use. An explicit `-c` path must exist; otherwise\n * we look for maestrostack.yml then maestrostack.yaml in the working directory.\n */\nexport async function resolveConfigPath(\n explicit: string | undefined,\n cwd: string = process.cwd(),\n): Promise<string> {\n if (explicit) {\n const resolved = path.resolve(cwd, explicit);\n if (!(await isFile(resolved))) {\n throw new MaestroStackError(`Config file not found: ${explicit}`);\n }\n return resolved;\n }\n\n for (const name of DEFAULT_CONFIG_NAMES) {\n const candidate = path.resolve(cwd, name);\n if (await isFile(candidate)) {\n return candidate;\n }\n }\n\n throw new MaestroStackError(\"maestrostack.yml not found.\", [\n \"Run `maestrostack init` to create one, or pass --config <path>.\",\n ]);\n}\n\n/**\n * Load, env-substitute, parse and validate a config file. The `.env` in the\n * working directory is loaded first so `${VAR}` tokens can resolve against it.\n */\nexport async function loadConfig(\n explicit: string | undefined,\n cwd: string = process.cwd(),\n): Promise<LoadedConfig> {\n // Load .env (does not override already-set process.env values).\n dotenv.config({ path: path.resolve(cwd, \".env\") });\n\n const configPath = await resolveConfigPath(explicit, cwd);\n logger.debug(`Using config: ${configPath}`);\n\n const raw = await readFile(configPath, \"utf8\");\n const substituted = resolveEnv(raw);\n\n let parsed: unknown;\n try {\n parsed = parseYaml(substituted);\n } catch (err) {\n const reason = err instanceof Error ? err.message : String(err);\n throw new MaestroStackError(`Invalid YAML in ${path.basename(configPath)}.`, [reason]);\n }\n\n const result = configSchema.safeParse(parsed);\n if (!result.success) {\n const details = result.error.issues.map((issue) => {\n const where = issue.path.length > 0 ? `${issue.path.join(\".\")}: ` : \"\";\n return `- ${where}${issue.message}`;\n });\n throw new MaestroStackError(\"Invalid configuration:\", details);\n }\n\n return { config: result.data, configPath };\n}\n","import { MaestroStackError } from \"../utils/errors.js\";\n\n/** Matches ${VAR_NAME} tokens. Variable names follow shell-ish conventions. */\nconst ENV_TOKEN = /\\$\\{([A-Za-z_][A-Za-z0-9_]*)\\}/g;\n\n/**\n * Substitute `${VAR}` tokens in the raw config text using `env` (defaults to\n * process.env). All missing variables are collected and reported together so the\n * user fixes them in one pass - and crucially this happens before any upload.\n */\nexport function resolveEnv(\n raw: string,\n env: NodeJS.ProcessEnv = process.env,\n): string {\n const missing = new Set<string>();\n\n const resolved = raw.replace(ENV_TOKEN, (_match, name: string) => {\n const value = env[name];\n if (value === undefined || value === \"\") {\n missing.add(name);\n return \"\";\n }\n return value;\n });\n\n if (missing.size > 0) {\n const names = [...missing].sort();\n throw new MaestroStackError(\n `Missing required environment variable(s): ${names.join(\", \")}`,\n [\"Set them in your shell or a .env file in the working directory.\"],\n );\n }\n\n return resolved;\n}\n","import { z } from \"zod\";\n\n/**\n * Zod schema for maestrostack.yml. Parsing yields a fully-typed {@link Config}.\n * Validation messages here are intentionally human-readable since they surface\n * directly in the CLI.\n */\n\nconst nonEmpty = (label: string) =>\n z.string({ required_error: `${label} is required` }).trim().min(1, `${label} must not be empty`);\n\nconst authSchema = z.object({\n username: nonEmpty(\"BrowserStack username (auth.username)\"),\n accessKey: nonEmpty(\"BrowserStack access key (auth.accessKey)\"),\n});\n\nconst appUploadSchema = z.object({\n source: z.literal(\"upload\"),\n path: nonEmpty(\"app.path\"),\n customId: z.string().trim().min(1).optional(),\n});\n\nconst appUrlSchema = z.object({\n source: z.literal(\"app_url\"),\n appUrl: nonEmpty(\"app.appUrl\").refine((v) => v.startsWith(\"bs://\"), {\n message: \"app.appUrl must start with bs://\",\n }),\n customId: z.string().trim().min(1).optional(),\n});\n\nconst appCustomIdSchema = z.object({\n // Present in the schema so configs validate, but resolution is deferred (see validate.ts).\n source: z.literal(\"custom_id\"),\n customId: nonEmpty(\"app.customId\"),\n});\n\nconst appSchema = z.discriminatedUnion(\"source\", [\n appUploadSchema,\n appUrlSchema,\n appCustomIdSchema,\n]);\n\nconst suiteSchema = z.object({\n root: z.string().trim().min(1).default(\".\"),\n packageName: z.string().trim().min(1).default(\"Flows.zip\"),\n customId: z.string().trim().min(1).optional(),\n include: z.array(nonEmpty(\"suite.include entry\")).default([\"**/*.yml\", \"**/*.yaml\"]),\n exclude: z.array(z.string()).default([]),\n});\n\nconst optionsSchema = z\n .object({\n networkLogs: z.boolean().optional(),\n deviceLogs: z.boolean().optional(),\n })\n .default({});\n\nconst runSchema = z.object({\n project: nonEmpty(\"run.project\"),\n devices: z\n .array(nonEmpty(\"device\"))\n .min(1, \"run.devices must contain at least one device\"),\n executeMode: z.enum([\"explicit\", \"main\"]).default(\"explicit\"),\n execute: z.array(nonEmpty(\"run.execute entry\")).optional(),\n options: optionsSchema,\n});\n\nexport const configSchema = z.object({\n version: z.literal(1, {\n errorMap: () => ({ message: \"Unsupported config version. Expected version: 1\" }),\n }),\n auth: authSchema,\n platform: z.enum([\"android\", \"ios\"], {\n errorMap: () => ({ message: \"Unsupported platform. Expected android or ios\" }),\n }),\n app: appSchema,\n suite: suiteSchema.default({}),\n run: runSchema,\n});\n\nexport type Config = z.infer<typeof configSchema>;\nexport type AppConfig = Config[\"app\"];\nexport type Platform = Config[\"platform\"];\n","import { createWriteStream } from \"node:fs\";\nimport path from \"node:path\";\nimport archiver from \"archiver\";\nimport { ensureDir } from \"../utils/fs.js\";\nimport { distDir } from \"../utils/paths.js\";\n\nexport interface ZipResult {\n /** Absolute path to the written zip file. */\n zipPath: string;\n /** Forward-slash flow paths that were included (relative to suite root). */\n files: string[];\n}\n\n/** Top-level folder inside the archive. BrowserStack locates flows under this. */\nexport const SUITE_PREFIX = \"Flows\";\n\n/**\n * Create the suite zip from the given flow files. Each entry is stored under\n * `Flows/<relativePath>` using forward slashes (required by the ZIP spec and so\n * BrowserStack can locate flows regardless of the host OS that built the zip).\n */\nexport async function createZip(options: {\n files: string[];\n suiteRoot: string;\n packageName: string;\n cwd?: string;\n}): Promise<ZipResult> {\n const cwd = options.cwd ?? process.cwd();\n const root = path.resolve(cwd, options.suiteRoot);\n const outDir = distDir(cwd);\n await ensureDir(outDir);\n const zipPath = path.join(outDir, options.packageName);\n\n await new Promise<void>((resolve, reject) => {\n const output = createWriteStream(zipPath);\n const archive = archiver(\"zip\", { zlib: { level: 9 } });\n\n output.on(\"close\", () => resolve());\n output.on(\"error\", reject);\n archive.on(\"error\", reject);\n archive.on(\"warning\", (err) => {\n if (err.code === \"ENOENT\") return; // non-fatal\n reject(err);\n });\n\n archive.pipe(output);\n\n for (const rel of options.files) {\n // `rel` is already forward-slash (from discoverFlows); resolve to the OS\n // path for reading, but name the entry with forward slashes.\n const absolute = path.join(root, ...rel.split(\"/\"));\n const entryName = `${SUITE_PREFIX}/${rel}`;\n archive.file(absolute, { name: entryName });\n }\n\n void archive.finalize();\n });\n\n return { zipPath, files: options.files };\n}\n","import path from \"node:path\";\n\n/**\n * Internal working directory MaestroStack writes to. Relative to the current\n * working directory so it lands next to the user's project. Should be gitignored.\n */\nexport const WORK_DIR = \".maestrostack\";\n\n/** Directory for build artifacts (the generated suite zip). */\nexport function distDir(cwd: string = process.cwd()): string {\n return path.join(cwd, WORK_DIR, \"dist\");\n}\n\n/** Directory for transient files. */\nexport function tmpDir(cwd: string = process.cwd()): string {\n return path.join(cwd, WORK_DIR, \"tmp\");\n}\n\n/**\n * Convert a path into the forward-slash form used internally and by BrowserStack\n * (zip entries, execute paths). Both separators are normalized so a config or\n * `--execute` value authored on Windows (`smoke\\login.yml`) matches discovered\n * flows regardless of the OS MaestroStack runs on.\n */\nexport function toPosix(p: string): string {\n return p.split(/[\\\\/]+/).join(\"/\");\n}\n","import path from \"node:path\";\nimport type { Config } from \"../config/schema.js\";\nimport { loadConfig, type LoadedConfig } from \"../config/loadConfig.js\";\nimport { discoverFlows } from \"../suite/discoverFlows.js\";\nimport { validateSuite } from \"../suite/validateSuite.js\";\nimport { MaestroStackError } from \"../utils/errors.js\";\nimport { extname, isFile } from \"../utils/fs.js\";\nimport { logger } from \"../utils/logger.js\";\n\nexport interface GlobalOptions {\n config?: string;\n debug?: boolean;\n}\n\nconst PLATFORM_EXT: Record<Config[\"platform\"], string> = {\n android: \".apk\",\n ios: \".ipa\",\n};\n\n/**\n * Semantic validation of the app block beyond the Zod schema: deferred sources,\n * file existence and platform/extension agreement.\n */\nexport async function validateApp(config: Config, cwd: string): Promise<void> {\n const { app, platform } = config;\n\n if (app.source === \"custom_id\") {\n throw new MaestroStackError(\n 'app.source \"custom_id\" is not supported in the MVP.',\n [\"Use source: upload (with a path) or source: app_url (with a bs:// url).\"],\n );\n }\n\n if (app.source === \"upload\") {\n const appPath = path.resolve(cwd, app.path);\n if (!(await isFile(appPath))) {\n throw new MaestroStackError(`app.path does not exist: ${app.path}`);\n }\n const expected = PLATFORM_EXT[platform];\n if (extname(appPath) !== expected) {\n throw new MaestroStackError(\n `Platform \"${platform}\" requires a ${expected} file, but app.path is \"${app.path}\".`,\n );\n }\n }\n}\n\n/**\n * Full validation pipeline shared by `validate` and `run`: app block, flow\n * discovery and suite/execute checks. Returns the discovered flow paths.\n */\nexport async function validateAll(\n loaded: LoadedConfig,\n cwd: string,\n): Promise<{ flows: string[] }> {\n const { config, configPath } = loaded;\n await validateApp(config, cwd);\n\n const flows = await discoverFlows(config.suite, {\n cwd,\n configBasename: path.basename(configPath),\n });\n\n await validateSuite(config, flows, cwd);\n\n return { flows };\n}\n\nexport async function validateCommand(opts: GlobalOptions): Promise<void> {\n const cwd = process.cwd();\n const loaded = await loadConfig(opts.config, cwd);\n const { flows } = await validateAll(loaded, cwd);\n const { config } = loaded;\n\n logger.info(\"Configuration is valid.\");\n logger.info(\"\");\n logger.info(`Config: ${path.relative(cwd, loaded.configPath) || loaded.configPath}`);\n logger.info(`Platform: ${config.platform}`);\n logger.info(`Project: ${config.run.project}`);\n logger.info(`App: ${describeApp(config)}`);\n logger.info(`Devices: ${config.run.devices.length}`);\n for (const device of config.run.devices) {\n logger.info(` - ${device}`);\n }\n logger.info(`Flows: ${flows.length}`);\n for (const flow of flows) {\n logger.info(` - ${flow}`);\n }\n logger.info(`Execute: ${config.run.executeMode}`);\n}\n\nfunction describeApp(config: Config): string {\n const { app } = config;\n switch (app.source) {\n case \"upload\":\n return `upload ${app.path}`;\n case \"app_url\":\n return `app_url ${app.appUrl}`;\n default:\n return app.source;\n }\n}\n","import path from \"node:path\";\nimport fg from \"fast-glob\";\nimport type { Config } from \"../config/schema.js\";\nimport { MaestroStackError } from \"../utils/errors.js\";\nimport { isDirectory } from \"../utils/fs.js\";\n\nexport interface DiscoverOptions {\n /** Working directory the suite root is resolved against. */\n cwd?: string;\n /** Basename of the active config file, always excluded from the suite. */\n configBasename?: string;\n}\n\n/**\n * Always-on ignore patterns. Root-level YAML files are treated as config files,\n * not Maestro flows (top-level only - `*.yml` matches `staging.yml` but not\n * `smoke/login.yml`). Build/VCS/dependency dirs are never packaged.\n */\nconst BUILT_IN_IGNORES = [\n \"*.yml\",\n \"*.yaml\",\n \".git/**\",\n \"node_modules/**\",\n \".maestrostack/**\",\n];\n\n/**\n * Discover Maestro flow files under `suite.root` using the configured include /\n * exclude globs. Returns sorted, forward-slash relative paths (fast-glob emits\n * POSIX separators on every OS, keeping output identical across platforms).\n */\nexport async function discoverFlows(\n suite: Config[\"suite\"],\n opts: DiscoverOptions = {},\n): Promise<string[]> {\n const cwd = opts.cwd ?? process.cwd();\n const root = path.resolve(cwd, suite.root);\n\n if (!(await isDirectory(root))) {\n throw new MaestroStackError(`suite.root does not exist: ${suite.root}`);\n }\n\n const ignore = [...BUILT_IN_IGNORES, ...suite.exclude];\n if (opts.configBasename) {\n ignore.push(opts.configBasename);\n }\n\n const matches = await fg(suite.include, {\n cwd: root,\n ignore,\n onlyFiles: true,\n dot: false,\n followSymbolicLinks: false,\n });\n\n if (matches.length === 0) {\n throw new MaestroStackError(\"No Maestro flow files found.\", [\n \"Maestro flow files should live inside subfolders (root-level YAML files are ignored).\",\n `Checked suite.root=\"${suite.root}\" with include=${JSON.stringify(suite.include)}.`,\n ]);\n }\n\n return matches.sort();\n}\n","import path from \"node:path\";\nimport type { Config } from \"../config/schema.js\";\nimport { MaestroStackError } from \"../utils/errors.js\";\nimport { isFile } from \"../utils/fs.js\";\nimport { toPosix } from \"../utils/paths.js\";\n\nexport const MAIN_FLOW = \"main.yaml\";\n\n/**\n * Validate the suite against the discovered flow set and the configured execute\n * mode.\n *\n * - explicit mode: every `run.execute` path must exist in the discovered flows.\n * - main mode: a `main.yaml` must exist at the suite root.\n *\n * `discovered` paths are forward-slash relative to the suite root; execute paths\n * supplied by users are normalized so Windows-style separators still match.\n */\nexport async function validateSuite(\n config: Config,\n discovered: string[],\n cwd: string = process.cwd(),\n): Promise<void> {\n const { run, suite } = config;\n\n if (run.executeMode === \"main\") {\n const mainPath = path.resolve(cwd, suite.root, MAIN_FLOW);\n if (!(await isFile(mainPath))) {\n throw new MaestroStackError(\n `executeMode is \"main\" but ${MAIN_FLOW} was not found in suite.root (${suite.root}).`,\n [`Add a ${MAIN_FLOW} or switch to executeMode: explicit with a run.execute list.`],\n );\n }\n return;\n }\n\n // explicit mode\n if (run.execute && run.execute.length > 0) {\n const available = new Set(discovered.map(toPosix));\n const missing = run.execute\n .map(toPosix)\n .filter((p) => !available.has(p));\n\n if (missing.length > 0) {\n throw new MaestroStackError(\n \"Configured execute file(s) do not exist in the packaged suite:\",\n missing.map((p) => `- ${p}`),\n );\n }\n }\n}\n","import path from \"node:path\";\nimport { loadConfig } from \"../config/loadConfig.js\";\nimport type { Config } from \"../config/schema.js\";\nimport { uploadApp } from \"../browserstack/uploadApp.js\";\nimport { uploadSuite } from \"../browserstack/uploadSuite.js\";\nimport { buildPath, buildPayload, startBuild } from \"../browserstack/startBuild.js\";\nimport { createZip } from \"../suite/createZip.js\";\nimport { MaestroStackError } from \"../utils/errors.js\";\nimport { logger } from \"../utils/logger.js\";\nimport { toPosix } from \"../utils/paths.js\";\nimport { validateAll, type GlobalOptions } from \"./validate.js\";\n\nexport interface RunOptions extends GlobalOptions {\n dryRun?: boolean;\n /** Temporary device override(s); replaces run.devices. */\n device?: string[];\n /** Temporary execute override(s); replaces run.execute (forces explicit mode). */\n execute?: string[];\n}\n\nconst APP_PLACEHOLDER = \"<resolved after upload>\";\nconst SUITE_PLACEHOLDER = \"<resolved after upload>\";\n\n/** Apply CLI overrides for devices / execute onto a loaded config. */\nexport function applyOverrides(config: Config, opts: RunOptions): Config {\n let run = config.run;\n if (opts.device && opts.device.length > 0) {\n run = { ...run, devices: opts.device };\n }\n if (opts.execute && opts.execute.length > 0) {\n run = { ...run, execute: opts.execute.map(toPosix), executeMode: \"explicit\" };\n }\n return { ...config, run };\n}\n\nexport async function runCommand(opts: RunOptions): Promise<void> {\n const cwd = process.cwd();\n const loaded = await loadConfig(opts.config, cwd);\n const config = applyOverrides(loaded.config, opts);\n\n // Validate with overrides applied.\n const { flows } = await validateAll({ ...loaded, config }, cwd);\n\n if (opts.dryRun) {\n printDryRun(config, flows, cwd);\n return;\n }\n\n // 1. Package the suite.\n const { zipPath } = await createZip({\n files: flows,\n suiteRoot: config.suite.root,\n packageName: config.suite.packageName,\n cwd,\n });\n logger.debug(`Created suite zip: ${zipPath}`);\n\n // 2. Resolve the app (upload local binary or use an existing bs:// url).\n const appUrl = await resolveApp(config, cwd);\n logger.debug(`Resolved app: ${appUrl}`);\n\n // 3. Upload the suite.\n logger.info(`Uploading test suite (${flows.length} flow(s)) ...`);\n const suiteResult = await uploadSuite({\n auth: config.auth,\n zipPath,\n customId: config.suite.customId,\n });\n\n // 4. Trigger the build.\n const payload = buildPayload(config, {\n app: appUrl,\n testSuite: suiteResult.test_suite_url,\n });\n logger.info(`Starting ${config.platform} build ...`);\n const build = await startBuild({ auth: config.auth, platform: config.platform, payload });\n\n printSummary(config, appUrl, suiteResult.test_suite_url, build.build_id);\n}\n\nasync function resolveApp(config: Config, cwd: string): Promise<string> {\n const { app } = config;\n if (app.source === \"app_url\") {\n return app.appUrl;\n }\n if (app.source === \"upload\") {\n logger.info(`Uploading app: ${app.path} ...`);\n const result = await uploadApp({\n auth: config.auth,\n filePath: path.resolve(cwd, app.path),\n customId: app.customId,\n });\n return result.app_url;\n }\n throw new MaestroStackError(`Unsupported app.source: ${app.source}`);\n}\n\nfunction previewApp(config: Config): string {\n const { app } = config;\n return app.source === \"app_url\" ? app.appUrl : APP_PLACEHOLDER;\n}\n\nfunction printDryRun(config: Config, flows: string[], cwd: string): void {\n logger.info(\"Dry run only. No API calls made.\");\n logger.info(\"\");\n logger.info(\"Would package:\");\n for (const flow of flows) {\n logger.info(`- ${flow}`);\n }\n logger.info(\"\");\n\n const { app } = config;\n if (app.source === \"upload\") {\n logger.info(`Would upload app:\\n${app.path}`);\n } else if (app.source === \"app_url\") {\n logger.info(`Would use app:\\n${app.appUrl}`);\n }\n logger.info(\"\");\n\n logger.info(`Would call:\\nPOST ${buildPath(config.platform)}`);\n logger.info(\"\");\n\n const payload = buildPayload(config, {\n app: previewApp(config),\n testSuite: SUITE_PLACEHOLDER,\n });\n logger.info(\"Payload:\");\n logger.info(JSON.stringify(payload, null, 2));\n}\n\nfunction printSummary(\n config: Config,\n appUrl: string,\n testSuiteUrl: string,\n buildId: string,\n): void {\n logger.info(\"\");\n logger.info(\"MaestroStack run started\");\n logger.info(\"\");\n logger.info(`Project: ${config.run.project}`);\n logger.info(`Platform: ${config.platform}`);\n logger.info(\"Devices:\");\n for (const device of config.run.devices) {\n logger.info(`- ${device}`);\n }\n logger.info(\"\");\n logger.info(`App: ${appUrl}`);\n logger.info(`Test suite: ${testSuiteUrl}`);\n logger.info(`Build ID: ${buildId}`);\n}\n","import { openAsBlob } from \"node:fs\";\nimport path from \"node:path\";\nimport { FormData } from \"undici\";\nimport { MaestroStackError } from \"../utils/errors.js\";\nimport { isFile } from \"../utils/fs.js\";\nimport { bsRequest, type BsAuth } from \"./client.js\";\n\nexport interface UploadAppResult {\n app_url: string;\n custom_id?: string;\n expiry?: string;\n app_name?: string;\n}\n\n/**\n * Upload a local app binary (.apk / .ipa) to BrowserStack.\n * POST /app-automate/maestro/v2/app (multipart: file, optional custom_id)\n */\nexport async function uploadApp(options: {\n auth: BsAuth;\n filePath: string;\n customId?: string;\n}): Promise<UploadAppResult> {\n if (!(await isFile(options.filePath))) {\n throw new MaestroStackError(`app.path does not exist: ${options.filePath}`);\n }\n\n const form = new FormData();\n const blob = await openAsBlob(options.filePath);\n form.set(\"file\", blob, path.basename(options.filePath));\n if (options.customId) {\n form.set(\"custom_id\", options.customId);\n }\n\n return bsRequest<UploadAppResult>({\n path: \"/app-automate/maestro/v2/app\",\n method: \"POST\",\n auth: options.auth,\n rawBody: form,\n });\n}\n","import { fetch, type BodyInit } from \"undici\";\nimport { MaestroStackError } from \"../utils/errors.js\";\nimport { logger } from \"../utils/logger.js\";\n\nexport const BASE_URL = \"https://api-cloud.browserstack.com\";\n\nexport interface BsAuth {\n username: string;\n accessKey: string;\n}\n\n/** Build an HTTP Basic auth header from BrowserStack credentials. */\nexport function authHeader(auth: BsAuth): string {\n const token = Buffer.from(`${auth.username}:${auth.accessKey}`).toString(\"base64\");\n return `Basic ${token}`;\n}\n\ninterface RequestOptions {\n path: string;\n method: \"GET\" | \"POST\";\n auth: BsAuth;\n /** JSON body - serialized and sent with application/json. */\n jsonBody?: unknown;\n /** Raw body (e.g. multipart FormData). undici sets the content-type. */\n rawBody?: BodyInit;\n}\n\n/**\n * Perform a request against the BrowserStack API with Basic auth, returning the\n * parsed JSON. Non-2xx responses become a {@link MaestroStackError} carrying the\n * status and any server message - credentials are never logged.\n */\nexport async function bsRequest<T>(options: RequestOptions): Promise<T> {\n const url = `${BASE_URL}${options.path}`;\n const headers: Record<string, string> = {\n authorization: authHeader(options.auth),\n };\n\n let body: BodyInit | undefined;\n if (options.jsonBody !== undefined) {\n headers[\"content-type\"] = \"application/json\";\n body = JSON.stringify(options.jsonBody);\n } else if (options.rawBody !== undefined) {\n body = options.rawBody;\n }\n\n logger.debug(`${options.method} ${url}`);\n\n let response: Awaited<ReturnType<typeof fetch>>;\n try {\n response = await fetch(url, { method: options.method, headers, body });\n } catch (err) {\n const reason = err instanceof Error ? err.message : String(err);\n throw new MaestroStackError(`Failed to reach BrowserStack: ${reason}`);\n }\n\n const text = await response.text();\n let data: unknown = {};\n if (text) {\n try {\n data = JSON.parse(text);\n } catch {\n data = { raw: text };\n }\n }\n\n if (!response.ok) {\n const message = extractErrorMessage(data) ?? response.statusText;\n throw new MaestroStackError(`BrowserStack API error (${response.status}): ${message}`);\n }\n\n return data as T;\n}\n\nfunction extractErrorMessage(data: unknown): string | undefined {\n if (data && typeof data === \"object\") {\n const obj = data as Record<string, unknown>;\n for (const key of [\"message\", \"error\", \"errors\"]) {\n const value = obj[key];\n if (typeof value === \"string\" && value.length > 0) return value;\n }\n }\n return undefined;\n}\n","import { openAsBlob } from \"node:fs\";\nimport path from \"node:path\";\nimport { FormData } from \"undici\";\nimport { MaestroStackError } from \"../utils/errors.js\";\nimport { isFile } from \"../utils/fs.js\";\nimport { bsRequest, type BsAuth } from \"./client.js\";\n\nexport interface UploadSuiteResult {\n test_suite_url: string;\n custom_id?: string;\n expiry?: string;\n test_suite_name?: string;\n framework?: string;\n}\n\n/**\n * Upload a zipped Maestro test suite to BrowserStack.\n * POST /app-automate/maestro/v2/test-suite (multipart: file, optional custom_id)\n */\nexport async function uploadSuite(options: {\n auth: BsAuth;\n zipPath: string;\n customId?: string;\n}): Promise<UploadSuiteResult> {\n if (!(await isFile(options.zipPath))) {\n throw new MaestroStackError(`Suite zip does not exist: ${options.zipPath}`);\n }\n\n const form = new FormData();\n const blob = await openAsBlob(options.zipPath);\n form.set(\"file\", blob, path.basename(options.zipPath));\n if (options.customId) {\n form.set(\"custom_id\", options.customId);\n }\n\n return bsRequest<UploadSuiteResult>({\n path: \"/app-automate/maestro/v2/test-suite\",\n method: \"POST\",\n auth: options.auth,\n rawBody: form,\n });\n}\n","import type { Config, Platform } from \"../config/schema.js\";\nimport { bsRequest, type BsAuth } from \"./client.js\";\n\nexport interface BuildPayload {\n app: string;\n testSuite: string;\n project: string;\n devices: string[];\n execute?: string[];\n networkLogs?: boolean;\n deviceLogs?: boolean;\n}\n\nexport interface StartBuildResult {\n message?: string;\n build_id: string;\n}\n\n/** Build endpoint path for the given platform. */\nexport function buildPath(platform: Platform): string {\n return `/app-automate/maestro/v2/${platform}/build`;\n}\n\n/**\n * Construct the BrowserStack build payload from config and the resolved app /\n * test-suite references. Pure and side-effect free so it can be unit-tested and\n * reused for the `--dry-run` preview.\n *\n * - `execute` is included only in explicit mode (in main mode BrowserStack runs\n * the suite's main.yaml).\n * - `networkLogs` / `deviceLogs` are JSON booleans, included only when set.\n */\nexport function buildPayload(\n config: Config,\n resolved: { app: string; testSuite: string },\n): BuildPayload {\n const { run } = config;\n const payload: BuildPayload = {\n app: resolved.app,\n testSuite: resolved.testSuite,\n project: run.project,\n devices: run.devices,\n };\n\n if (run.executeMode === \"explicit\" && run.execute && run.execute.length > 0) {\n payload.execute = run.execute;\n }\n\n if (run.options.networkLogs !== undefined) {\n payload.networkLogs = run.options.networkLogs;\n }\n if (run.options.deviceLogs !== undefined) {\n payload.deviceLogs = run.options.deviceLogs;\n }\n\n return payload;\n}\n\n/** Trigger a Maestro build and return the BrowserStack build id. */\nexport async function startBuild(options: {\n auth: BsAuth;\n platform: Platform;\n payload: BuildPayload;\n}): Promise<StartBuildResult> {\n return bsRequest<StartBuildResult>({\n path: buildPath(options.platform),\n method: \"POST\",\n auth: options.auth,\n jsonBody: options.payload,\n });\n}\n","import path from \"node:path\";\nimport { loadConfig } from \"../config/loadConfig.js\";\nimport { uploadApp } from \"../browserstack/uploadApp.js\";\nimport { MaestroStackError } from \"../utils/errors.js\";\nimport { logger } from \"../utils/logger.js\";\nimport { validateApp, type GlobalOptions } from \"./validate.js\";\n\n/**\n * Upload only the app and print the resulting BrowserStack app_url. Only valid\n * for app.source: upload.\n */\nexport async function uploadAppCommand(opts: GlobalOptions): Promise<void> {\n const cwd = process.cwd();\n const { config } = await loadConfig(opts.config, cwd);\n await validateApp(config, cwd);\n\n if (config.app.source !== \"upload\") {\n throw new MaestroStackError(\n `upload-app requires app.source \"upload\" (current: \"${config.app.source}\").`,\n );\n }\n\n const filePath = path.resolve(cwd, config.app.path);\n logger.info(`Uploading app: ${config.app.path} ...`);\n\n const result = await uploadApp({\n auth: config.auth,\n filePath,\n customId: config.app.customId,\n });\n\n logger.info(\"Uploaded app:\");\n logger.info(`app_url: ${result.app_url}`);\n if (result.custom_id) logger.info(`custom_id: ${result.custom_id}`);\n if (result.expiry) logger.info(`expires: ${result.expiry}`);\n}\n","import { loadConfig } from \"../config/loadConfig.js\";\nimport { uploadSuite } from \"../browserstack/uploadSuite.js\";\nimport { createZip } from \"../suite/createZip.js\";\nimport { logger } from \"../utils/logger.js\";\nimport { validateAll, type GlobalOptions } from \"./validate.js\";\n\n/** Package and upload only the Maestro suite, printing the test_suite_url. */\nexport async function uploadSuiteCommand(opts: GlobalOptions): Promise<void> {\n const cwd = process.cwd();\n const loaded = await loadConfig(opts.config, cwd);\n const { config } = loaded;\n const { flows } = await validateAll(loaded, cwd);\n\n const { zipPath } = await createZip({\n files: flows,\n suiteRoot: config.suite.root,\n packageName: config.suite.packageName,\n cwd,\n });\n\n logger.info(`Uploading test suite (${flows.length} flow(s)) ...`);\n\n const result = await uploadSuite({\n auth: config.auth,\n zipPath,\n customId: config.suite.customId,\n });\n\n logger.info(\"Uploaded test suite:\");\n logger.info(`test_suite_url: ${result.test_suite_url}`);\n if (result.custom_id) logger.info(`custom_id: ${result.custom_id}`);\n if (result.expiry) logger.info(`expires: ${result.expiry}`);\n}\n"],"mappings":";;;AAAA,SAAS,eAAe;;;ACAxB,SAAS,iBAAiB;AAC1B,OAAOA,WAAU;;;ACIV,IAAM,oBAAN,cAAgC,MAAM;AAAA;AAAA,EAElC;AAAA,EAET,YAAY,SAAiB,SAAoB;AAC/C,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,SAAK,UAAU;AAAA,EACjB;AACF;AAGO,SAAS,oBAAoB,KAAwC;AAC1E,SAAO,eAAe;AACxB;;;ACnBA,SAAS,iBAAiB;AAC1B,OAAO,QAAQ;AACf,OAAO,UAAU;AAGjB,eAAsB,WAAW,GAA6B;AAC5D,MAAI;AACF,UAAM,GAAG,OAAO,GAAG,UAAU,IAAI;AACjC,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAGA,eAAsB,OAAO,GAA6B;AACxD,MAAI;AACF,UAAM,OAAO,MAAM,GAAG,KAAK,CAAC;AAC5B,WAAO,KAAK,OAAO;AAAA,EACrB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAGA,eAAsB,YAAY,GAA6B;AAC7D,MAAI;AACF,UAAM,OAAO,MAAM,GAAG,KAAK,CAAC;AAC5B,WAAO,KAAK,YAAY;AAAA,EAC1B,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAGA,eAAsB,UAAU,KAA4B;AAC1D,QAAM,GAAG,MAAM,KAAK,EAAE,WAAW,KAAK,CAAC;AACzC;AAQO,SAAS,QAAQ,GAAmB;AACzC,SAAO,KAAK,QAAQ,CAAC,EAAE,YAAY;AACrC;;;AC7CA,IAAI,eAAe;AAGZ,SAAS,SAAS,SAAwB;AAC/C,iBAAe;AACjB;AAMO,IAAM,SAAS;AAAA;AAAA,EAEpB,KAAK,UAAU,IAAU;AACvB,YAAQ,IAAI,OAAO;AAAA,EACrB;AAAA,EACA,KAAK,SAAuB;AAC1B,YAAQ,KAAK,YAAY,OAAO,EAAE;AAAA,EACpC;AAAA,EACA,MAAM,SAAuB;AAC3B,YAAQ,MAAM,OAAO;AAAA,EACvB;AAAA;AAAA,EAEA,MAAM,SAAuB;AAC3B,QAAI,cAAc;AAChB,cAAQ,MAAM,WAAW,OAAO,EAAE;AAAA,IACpC;AAAA,EACF;AACF;;;AHjBA,IAAM,cAAc;AAGpB,eAAsB,YAAY,MAAkC;AAClE,MAAI,KAAK,WAAW,KAAK,KAAK;AAC5B,UAAM,IAAI,kBAAkB,sCAAsC;AAAA,EACpE;AACA,QAAM,WAAqB,KAAK,MAAM,QAAQ;AAE9C,QAAM,MAAM,QAAQ,IAAI;AACxB,QAAM,SAASC,MAAK,KAAK,KAAK,WAAW;AAEzC,MAAK,MAAM,WAAW,MAAM,KAAM,CAAC,KAAK,OAAO;AAC7C,UAAM,IAAI,kBAAkB,GAAG,WAAW,oBAAoB;AAAA,MAC5D;AAAA,IACF,CAAC;AAAA,EACH;AAEA,QAAM,UAAU,QAAQ,SAAS,QAAQ,GAAG,MAAM;AAClD,SAAO,KAAK,WAAW,WAAW,eAAe,QAAQ,IAAI;AAC7D,SAAO,KAAK,8FAA8F;AAC5G;AAEA,SAAS,SAAS,UAA4B;AAC5C,QAAM,UAAU,aAAa,QAAQ,2BAA2B;AAChE,QAAM,UACJ,aAAa,QACT,CAAC,gBAAgB,IACjB,CAAC,2BAA2B,qBAAqB;AAEvD,QAAM,cAAc,QAAQ,IAAI,CAAC,MAAM,SAAS,CAAC,EAAE,EAAE,KAAK,IAAI;AAG9D,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,aAAa,QAAQ;AAAA,IACrB;AAAA,IACA;AAAA,IACA;AAAA,IACA,WAAW,OAAO;AAAA,IAClB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,EAAE,KAAK,IAAI;AACb;;;AIlFA,OAAOC,WAAU;;;ACAjB,SAAS,gBAAgB;AACzB,OAAOC,WAAU;AACjB,OAAO,YAAY;AACnB,SAAS,SAAS,iBAAiB;;;ACAnC,IAAM,YAAY;AAOX,SAAS,WACd,KACA,MAAyB,QAAQ,KACzB;AACR,QAAM,UAAU,oBAAI,IAAY;AAEhC,QAAM,WAAW,IAAI,QAAQ,WAAW,CAAC,QAAQ,SAAiB;AAChE,UAAM,QAAQ,IAAI,IAAI;AACtB,QAAI,UAAU,UAAa,UAAU,IAAI;AACvC,cAAQ,IAAI,IAAI;AAChB,aAAO;AAAA,IACT;AACA,WAAO;AAAA,EACT,CAAC;AAED,MAAI,QAAQ,OAAO,GAAG;AACpB,UAAM,QAAQ,CAAC,GAAG,OAAO,EAAE,KAAK;AAChC,UAAM,IAAI;AAAA,MACR,6CAA6C,MAAM,KAAK,IAAI,CAAC;AAAA,MAC7D,CAAC,iEAAiE;AAAA,IACpE;AAAA,EACF;AAEA,SAAO;AACT;;;AClCA,SAAS,SAAS;AAQlB,IAAM,WAAW,CAAC,UAChB,EAAE,OAAO,EAAE,gBAAgB,GAAG,KAAK,eAAe,CAAC,EAAE,KAAK,EAAE,IAAI,GAAG,GAAG,KAAK,oBAAoB;AAEjG,IAAM,aAAa,EAAE,OAAO;AAAA,EAC1B,UAAU,SAAS,uCAAuC;AAAA,EAC1D,WAAW,SAAS,0CAA0C;AAChE,CAAC;AAED,IAAM,kBAAkB,EAAE,OAAO;AAAA,EAC/B,QAAQ,EAAE,QAAQ,QAAQ;AAAA,EAC1B,MAAM,SAAS,UAAU;AAAA,EACzB,UAAU,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,EAAE,SAAS;AAC9C,CAAC;AAED,IAAM,eAAe,EAAE,OAAO;AAAA,EAC5B,QAAQ,EAAE,QAAQ,SAAS;AAAA,EAC3B,QAAQ,SAAS,YAAY,EAAE,OAAO,CAAC,MAAM,EAAE,WAAW,OAAO,GAAG;AAAA,IAClE,SAAS;AAAA,EACX,CAAC;AAAA,EACD,UAAU,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,EAAE,SAAS;AAC9C,CAAC;AAED,IAAM,oBAAoB,EAAE,OAAO;AAAA;AAAA,EAEjC,QAAQ,EAAE,QAAQ,WAAW;AAAA,EAC7B,UAAU,SAAS,cAAc;AACnC,CAAC;AAED,IAAM,YAAY,EAAE,mBAAmB,UAAU;AAAA,EAC/C;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAED,IAAM,cAAc,EAAE,OAAO;AAAA,EAC3B,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,EAAE,QAAQ,GAAG;AAAA,EAC1C,aAAa,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,EAAE,QAAQ,WAAW;AAAA,EACzD,UAAU,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,EAAE,SAAS;AAAA,EAC5C,SAAS,EAAE,MAAM,SAAS,qBAAqB,CAAC,EAAE,QAAQ,CAAC,YAAY,WAAW,CAAC;AAAA,EACnF,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,QAAQ,CAAC,CAAC;AACzC,CAAC;AAED,IAAM,gBAAgB,EACnB,OAAO;AAAA,EACN,aAAa,EAAE,QAAQ,EAAE,SAAS;AAAA,EAClC,YAAY,EAAE,QAAQ,EAAE,SAAS;AACnC,CAAC,EACA,QAAQ,CAAC,CAAC;AAEb,IAAM,YAAY,EAAE,OAAO;AAAA,EACzB,SAAS,SAAS,aAAa;AAAA,EAC/B,SAAS,EACN,MAAM,SAAS,QAAQ,CAAC,EACxB,IAAI,GAAG,8CAA8C;AAAA,EACxD,aAAa,EAAE,KAAK,CAAC,YAAY,MAAM,CAAC,EAAE,QAAQ,UAAU;AAAA,EAC5D,SAAS,EAAE,MAAM,SAAS,mBAAmB,CAAC,EAAE,SAAS;AAAA,EACzD,SAAS;AACX,CAAC;AAEM,IAAM,eAAe,EAAE,OAAO;AAAA,EACnC,SAAS,EAAE,QAAQ,GAAG;AAAA,IACpB,UAAU,OAAO,EAAE,SAAS,kDAAkD;AAAA,EAChF,CAAC;AAAA,EACD,MAAM;AAAA,EACN,UAAU,EAAE,KAAK,CAAC,WAAW,KAAK,GAAG;AAAA,IACnC,UAAU,OAAO,EAAE,SAAS,gDAAgD;AAAA,EAC9E,CAAC;AAAA,EACD,KAAK;AAAA,EACL,OAAO,YAAY,QAAQ,CAAC,CAAC;AAAA,EAC7B,KAAK;AACP,CAAC;;;AFpED,IAAM,uBAAuB,CAAC,oBAAoB,mBAAmB;AAYrE,eAAsB,kBACpB,UACA,MAAc,QAAQ,IAAI,GACT;AACjB,MAAI,UAAU;AACZ,UAAM,WAAWC,MAAK,QAAQ,KAAK,QAAQ;AAC3C,QAAI,CAAE,MAAM,OAAO,QAAQ,GAAI;AAC7B,YAAM,IAAI,kBAAkB,0BAA0B,QAAQ,EAAE;AAAA,IAClE;AACA,WAAO;AAAA,EACT;AAEA,aAAW,QAAQ,sBAAsB;AACvC,UAAM,YAAYA,MAAK,QAAQ,KAAK,IAAI;AACxC,QAAI,MAAM,OAAO,SAAS,GAAG;AAC3B,aAAO;AAAA,IACT;AAAA,EACF;AAEA,QAAM,IAAI,kBAAkB,+BAA+B;AAAA,IACzD;AAAA,EACF,CAAC;AACH;AAMA,eAAsB,WACpB,UACA,MAAc,QAAQ,IAAI,GACH;AAEvB,SAAO,OAAO,EAAE,MAAMA,MAAK,QAAQ,KAAK,MAAM,EAAE,CAAC;AAEjD,QAAM,aAAa,MAAM,kBAAkB,UAAU,GAAG;AACxD,SAAO,MAAM,iBAAiB,UAAU,EAAE;AAE1C,QAAM,MAAM,MAAM,SAAS,YAAY,MAAM;AAC7C,QAAM,cAAc,WAAW,GAAG;AAElC,MAAI;AACJ,MAAI;AACF,aAAS,UAAU,WAAW;AAAA,EAChC,SAAS,KAAK;AACZ,UAAM,SAAS,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC9D,UAAM,IAAI,kBAAkB,mBAAmBA,MAAK,SAAS,UAAU,CAAC,KAAK,CAAC,MAAM,CAAC;AAAA,EACvF;AAEA,QAAM,SAAS,aAAa,UAAU,MAAM;AAC5C,MAAI,CAAC,OAAO,SAAS;AACnB,UAAM,UAAU,OAAO,MAAM,OAAO,IAAI,CAAC,UAAU;AACjD,YAAM,QAAQ,MAAM,KAAK,SAAS,IAAI,GAAG,MAAM,KAAK,KAAK,GAAG,CAAC,OAAO;AACpE,aAAO,KAAK,KAAK,GAAG,MAAM,OAAO;AAAA,IACnC,CAAC;AACD,UAAM,IAAI,kBAAkB,0BAA0B,OAAO;AAAA,EAC/D;AAEA,SAAO,EAAE,QAAQ,OAAO,MAAM,WAAW;AAC3C;;;AGjFA,SAAS,yBAAyB;AAClC,OAAOC,WAAU;AACjB,OAAO,cAAc;;;ACFrB,OAAOC,WAAU;AAMV,IAAM,WAAW;AAGjB,SAAS,QAAQ,MAAc,QAAQ,IAAI,GAAW;AAC3D,SAAOA,MAAK,KAAK,KAAK,UAAU,MAAM;AACxC;AAaO,SAAS,QAAQ,GAAmB;AACzC,SAAO,EAAE,MAAM,QAAQ,EAAE,KAAK,GAAG;AACnC;;;ADZO,IAAM,eAAe;AAO5B,eAAsB,UAAU,SAKT;AACrB,QAAM,MAAM,QAAQ,OAAO,QAAQ,IAAI;AACvC,QAAM,OAAOC,MAAK,QAAQ,KAAK,QAAQ,SAAS;AAChD,QAAM,SAAS,QAAQ,GAAG;AAC1B,QAAM,UAAU,MAAM;AACtB,QAAM,UAAUA,MAAK,KAAK,QAAQ,QAAQ,WAAW;AAErD,QAAM,IAAI,QAAc,CAAC,SAAS,WAAW;AAC3C,UAAM,SAAS,kBAAkB,OAAO;AACxC,UAAM,UAAU,SAAS,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,CAAC;AAEtD,WAAO,GAAG,SAAS,MAAM,QAAQ,CAAC;AAClC,WAAO,GAAG,SAAS,MAAM;AACzB,YAAQ,GAAG,SAAS,MAAM;AAC1B,YAAQ,GAAG,WAAW,CAAC,QAAQ;AAC7B,UAAI,IAAI,SAAS,SAAU;AAC3B,aAAO,GAAG;AAAA,IACZ,CAAC;AAED,YAAQ,KAAK,MAAM;AAEnB,eAAW,OAAO,QAAQ,OAAO;AAG/B,YAAM,WAAWA,MAAK,KAAK,MAAM,GAAG,IAAI,MAAM,GAAG,CAAC;AAClD,YAAM,YAAY,GAAG,YAAY,IAAI,GAAG;AACxC,cAAQ,KAAK,UAAU,EAAE,MAAM,UAAU,CAAC;AAAA,IAC5C;AAEA,SAAK,QAAQ,SAAS;AAAA,EACxB,CAAC;AAED,SAAO,EAAE,SAAS,OAAO,QAAQ,MAAM;AACzC;;;AE3DA,OAAOC,WAAU;;;ACAjB,OAAOC,WAAU;AACjB,OAAO,QAAQ;AAiBf,IAAM,mBAAmB;AAAA,EACvB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAOA,eAAsB,cACpB,OACA,OAAwB,CAAC,GACN;AACnB,QAAM,MAAM,KAAK,OAAO,QAAQ,IAAI;AACpC,QAAM,OAAOC,MAAK,QAAQ,KAAK,MAAM,IAAI;AAEzC,MAAI,CAAE,MAAM,YAAY,IAAI,GAAI;AAC9B,UAAM,IAAI,kBAAkB,8BAA8B,MAAM,IAAI,EAAE;AAAA,EACxE;AAEA,QAAM,SAAS,CAAC,GAAG,kBAAkB,GAAG,MAAM,OAAO;AACrD,MAAI,KAAK,gBAAgB;AACvB,WAAO,KAAK,KAAK,cAAc;AAAA,EACjC;AAEA,QAAM,UAAU,MAAM,GAAG,MAAM,SAAS;AAAA,IACtC,KAAK;AAAA,IACL;AAAA,IACA,WAAW;AAAA,IACX,KAAK;AAAA,IACL,qBAAqB;AAAA,EACvB,CAAC;AAED,MAAI,QAAQ,WAAW,GAAG;AACxB,UAAM,IAAI,kBAAkB,gCAAgC;AAAA,MAC1D;AAAA,MACA,uBAAuB,MAAM,IAAI,kBAAkB,KAAK,UAAU,MAAM,OAAO,CAAC;AAAA,IAClF,CAAC;AAAA,EACH;AAEA,SAAO,QAAQ,KAAK;AACtB;;;AC/DA,OAAOC,WAAU;AAMV,IAAM,YAAY;AAYzB,eAAsB,cACpB,QACA,YACA,MAAc,QAAQ,IAAI,GACX;AACf,QAAM,EAAE,KAAK,MAAM,IAAI;AAEvB,MAAI,IAAI,gBAAgB,QAAQ;AAC9B,UAAM,WAAWC,MAAK,QAAQ,KAAK,MAAM,MAAM,SAAS;AACxD,QAAI,CAAE,MAAM,OAAO,QAAQ,GAAI;AAC7B,YAAM,IAAI;AAAA,QACR,6BAA6B,SAAS,iCAAiC,MAAM,IAAI;AAAA,QACjF,CAAC,SAAS,SAAS,8DAA8D;AAAA,MACnF;AAAA,IACF;AACA;AAAA,EACF;AAGA,MAAI,IAAI,WAAW,IAAI,QAAQ,SAAS,GAAG;AACzC,UAAM,YAAY,IAAI,IAAI,WAAW,IAAI,OAAO,CAAC;AACjD,UAAM,UAAU,IAAI,QACjB,IAAI,OAAO,EACX,OAAO,CAAC,MAAM,CAAC,UAAU,IAAI,CAAC,CAAC;AAElC,QAAI,QAAQ,SAAS,GAAG;AACtB,YAAM,IAAI;AAAA,QACR;AAAA,QACA,QAAQ,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE;AAAA,MAC7B;AAAA,IACF;AAAA,EACF;AACF;;;AFpCA,IAAM,eAAmD;AAAA,EACvD,SAAS;AAAA,EACT,KAAK;AACP;AAMA,eAAsB,YAAY,QAAgB,KAA4B;AAC5E,QAAM,EAAE,KAAK,SAAS,IAAI;AAE1B,MAAI,IAAI,WAAW,aAAa;AAC9B,UAAM,IAAI;AAAA,MACR;AAAA,MACA,CAAC,yEAAyE;AAAA,IAC5E;AAAA,EACF;AAEA,MAAI,IAAI,WAAW,UAAU;AAC3B,UAAM,UAAUC,MAAK,QAAQ,KAAK,IAAI,IAAI;AAC1C,QAAI,CAAE,MAAM,OAAO,OAAO,GAAI;AAC5B,YAAM,IAAI,kBAAkB,4BAA4B,IAAI,IAAI,EAAE;AAAA,IACpE;AACA,UAAM,WAAW,aAAa,QAAQ;AACtC,QAAI,QAAQ,OAAO,MAAM,UAAU;AACjC,YAAM,IAAI;AAAA,QACR,aAAa,QAAQ,gBAAgB,QAAQ,2BAA2B,IAAI,IAAI;AAAA,MAClF;AAAA,IACF;AAAA,EACF;AACF;AAMA,eAAsB,YACpB,QACA,KAC8B;AAC9B,QAAM,EAAE,QAAQ,WAAW,IAAI;AAC/B,QAAM,YAAY,QAAQ,GAAG;AAE7B,QAAM,QAAQ,MAAM,cAAc,OAAO,OAAO;AAAA,IAC9C;AAAA,IACA,gBAAgBA,MAAK,SAAS,UAAU;AAAA,EAC1C,CAAC;AAED,QAAM,cAAc,QAAQ,OAAO,GAAG;AAEtC,SAAO,EAAE,MAAM;AACjB;AAEA,eAAsB,gBAAgB,MAAoC;AACxE,QAAM,MAAM,QAAQ,IAAI;AACxB,QAAM,SAAS,MAAM,WAAW,KAAK,QAAQ,GAAG;AAChD,QAAM,EAAE,MAAM,IAAI,MAAM,YAAY,QAAQ,GAAG;AAC/C,QAAM,EAAE,OAAO,IAAI;AAEnB,SAAO,KAAK,yBAAyB;AACrC,SAAO,KAAK,EAAE;AACd,SAAO,KAAK,cAAcA,MAAK,SAAS,KAAK,OAAO,UAAU,KAAK,OAAO,UAAU,EAAE;AACtF,SAAO,KAAK,cAAc,OAAO,QAAQ,EAAE;AAC3C,SAAO,KAAK,cAAc,OAAO,IAAI,OAAO,EAAE;AAC9C,SAAO,KAAK,cAAc,YAAY,MAAM,CAAC,EAAE;AAC/C,SAAO,KAAK,cAAc,OAAO,IAAI,QAAQ,MAAM,EAAE;AACrD,aAAW,UAAU,OAAO,IAAI,SAAS;AACvC,WAAO,KAAK,OAAO,MAAM,EAAE;AAAA,EAC7B;AACA,SAAO,KAAK,cAAc,MAAM,MAAM,EAAE;AACxC,aAAW,QAAQ,OAAO;AACxB,WAAO,KAAK,OAAO,IAAI,EAAE;AAAA,EAC3B;AACA,SAAO,KAAK,cAAc,OAAO,IAAI,WAAW,EAAE;AACpD;AAEA,SAAS,YAAY,QAAwB;AAC3C,QAAM,EAAE,IAAI,IAAI;AAChB,UAAQ,IAAI,QAAQ;AAAA,IAClB,KAAK;AACH,aAAO,UAAU,IAAI,IAAI;AAAA,IAC3B,KAAK;AACH,aAAO,WAAW,IAAI,MAAM;AAAA,IAC9B;AACE,aAAO,IAAI;AAAA,EACf;AACF;;;AN3FA,eAAsB,eAAe,MAAoC;AACvE,QAAM,MAAM,QAAQ,IAAI;AACxB,QAAM,SAAS,MAAM,WAAW,KAAK,QAAQ,GAAG;AAChD,QAAM,EAAE,OAAO,IAAI;AACnB,QAAM,EAAE,MAAM,IAAI,MAAM,YAAY,QAAQ,GAAG;AAE/C,QAAM,EAAE,QAAQ,IAAI,MAAM,UAAU;AAAA,IAClC,OAAO;AAAA,IACP,WAAW,OAAO,MAAM;AAAA,IACxB,aAAa,OAAO,MAAM;AAAA,IAC1B;AAAA,EACF,CAAC;AAED,SAAO,KAAK,sBAAsBC,MAAK,SAAS,KAAK,OAAO,KAAK,OAAO,EAAE;AAC1E,SAAO,KAAK,iBAAiB;AAC7B,aAAW,QAAQ,OAAO;AACxB,WAAO,KAAK,KAAK,IAAI,EAAE;AAAA,EACzB;AACF;;;AS5BA,OAAOC,YAAU;;;ACAjB,SAAS,kBAAkB;AAC3B,OAAOC,YAAU;AACjB,SAAS,gBAAgB;;;ACFzB,SAAS,aAA4B;AAI9B,IAAM,WAAW;AAQjB,SAAS,WAAW,MAAsB;AAC/C,QAAM,QAAQ,OAAO,KAAK,GAAG,KAAK,QAAQ,IAAI,KAAK,SAAS,EAAE,EAAE,SAAS,QAAQ;AACjF,SAAO,SAAS,KAAK;AACvB;AAiBA,eAAsB,UAAa,SAAqC;AACtE,QAAM,MAAM,GAAG,QAAQ,GAAG,QAAQ,IAAI;AACtC,QAAM,UAAkC;AAAA,IACtC,eAAe,WAAW,QAAQ,IAAI;AAAA,EACxC;AAEA,MAAI;AACJ,MAAI,QAAQ,aAAa,QAAW;AAClC,YAAQ,cAAc,IAAI;AAC1B,WAAO,KAAK,UAAU,QAAQ,QAAQ;AAAA,EACxC,WAAW,QAAQ,YAAY,QAAW;AACxC,WAAO,QAAQ;AAAA,EACjB;AAEA,SAAO,MAAM,GAAG,QAAQ,MAAM,IAAI,GAAG,EAAE;AAEvC,MAAI;AACJ,MAAI;AACF,eAAW,MAAM,MAAM,KAAK,EAAE,QAAQ,QAAQ,QAAQ,SAAS,KAAK,CAAC;AAAA,EACvE,SAAS,KAAK;AACZ,UAAM,SAAS,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC9D,UAAM,IAAI,kBAAkB,iCAAiC,MAAM,EAAE;AAAA,EACvE;AAEA,QAAM,OAAO,MAAM,SAAS,KAAK;AACjC,MAAI,OAAgB,CAAC;AACrB,MAAI,MAAM;AACR,QAAI;AACF,aAAO,KAAK,MAAM,IAAI;AAAA,IACxB,QAAQ;AACN,aAAO,EAAE,KAAK,KAAK;AAAA,IACrB;AAAA,EACF;AAEA,MAAI,CAAC,SAAS,IAAI;AAChB,UAAM,UAAU,oBAAoB,IAAI,KAAK,SAAS;AACtD,UAAM,IAAI,kBAAkB,2BAA2B,SAAS,MAAM,MAAM,OAAO,EAAE;AAAA,EACvF;AAEA,SAAO;AACT;AAEA,SAAS,oBAAoB,MAAmC;AAC9D,MAAI,QAAQ,OAAO,SAAS,UAAU;AACpC,UAAM,MAAM;AACZ,eAAW,OAAO,CAAC,WAAW,SAAS,QAAQ,GAAG;AAChD,YAAM,QAAQ,IAAI,GAAG;AACrB,UAAI,OAAO,UAAU,YAAY,MAAM,SAAS,EAAG,QAAO;AAAA,IAC5D;AAAA,EACF;AACA,SAAO;AACT;;;ADjEA,eAAsB,UAAU,SAIH;AAC3B,MAAI,CAAE,MAAM,OAAO,QAAQ,QAAQ,GAAI;AACrC,UAAM,IAAI,kBAAkB,4BAA4B,QAAQ,QAAQ,EAAE;AAAA,EAC5E;AAEA,QAAM,OAAO,IAAI,SAAS;AAC1B,QAAM,OAAO,MAAM,WAAW,QAAQ,QAAQ;AAC9C,OAAK,IAAI,QAAQ,MAAMC,OAAK,SAAS,QAAQ,QAAQ,CAAC;AACtD,MAAI,QAAQ,UAAU;AACpB,SAAK,IAAI,aAAa,QAAQ,QAAQ;AAAA,EACxC;AAEA,SAAO,UAA2B;AAAA,IAChC,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,MAAM,QAAQ;AAAA,IACd,SAAS;AAAA,EACX,CAAC;AACH;;;AExCA,SAAS,cAAAC,mBAAkB;AAC3B,OAAOC,YAAU;AACjB,SAAS,YAAAC,iBAAgB;AAiBzB,eAAsB,YAAY,SAIH;AAC7B,MAAI,CAAE,MAAM,OAAO,QAAQ,OAAO,GAAI;AACpC,UAAM,IAAI,kBAAkB,6BAA6B,QAAQ,OAAO,EAAE;AAAA,EAC5E;AAEA,QAAM,OAAO,IAAIC,UAAS;AAC1B,QAAM,OAAO,MAAMC,YAAW,QAAQ,OAAO;AAC7C,OAAK,IAAI,QAAQ,MAAMC,OAAK,SAAS,QAAQ,OAAO,CAAC;AACrD,MAAI,QAAQ,UAAU;AACpB,SAAK,IAAI,aAAa,QAAQ,QAAQ;AAAA,EACxC;AAEA,SAAO,UAA6B;AAAA,IAClC,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,MAAM,QAAQ;AAAA,IACd,SAAS;AAAA,EACX,CAAC;AACH;;;ACtBO,SAAS,UAAU,UAA4B;AACpD,SAAO,4BAA4B,QAAQ;AAC7C;AAWO,SAAS,aACd,QACA,UACc;AACd,QAAM,EAAE,IAAI,IAAI;AAChB,QAAM,UAAwB;AAAA,IAC5B,KAAK,SAAS;AAAA,IACd,WAAW,SAAS;AAAA,IACpB,SAAS,IAAI;AAAA,IACb,SAAS,IAAI;AAAA,EACf;AAEA,MAAI,IAAI,gBAAgB,cAAc,IAAI,WAAW,IAAI,QAAQ,SAAS,GAAG;AAC3E,YAAQ,UAAU,IAAI;AAAA,EACxB;AAEA,MAAI,IAAI,QAAQ,gBAAgB,QAAW;AACzC,YAAQ,cAAc,IAAI,QAAQ;AAAA,EACpC;AACA,MAAI,IAAI,QAAQ,eAAe,QAAW;AACxC,YAAQ,aAAa,IAAI,QAAQ;AAAA,EACnC;AAEA,SAAO;AACT;AAGA,eAAsB,WAAW,SAIH;AAC5B,SAAO,UAA4B;AAAA,IACjC,MAAM,UAAU,QAAQ,QAAQ;AAAA,IAChC,QAAQ;AAAA,IACR,MAAM,QAAQ;AAAA,IACd,UAAU,QAAQ;AAAA,EACpB,CAAC;AACH;;;AJlDA,IAAM,kBAAkB;AACxB,IAAM,oBAAoB;AAGnB,SAAS,eAAe,QAAgB,MAA0B;AACvE,MAAI,MAAM,OAAO;AACjB,MAAI,KAAK,UAAU,KAAK,OAAO,SAAS,GAAG;AACzC,UAAM,EAAE,GAAG,KAAK,SAAS,KAAK,OAAO;AAAA,EACvC;AACA,MAAI,KAAK,WAAW,KAAK,QAAQ,SAAS,GAAG;AAC3C,UAAM,EAAE,GAAG,KAAK,SAAS,KAAK,QAAQ,IAAI,OAAO,GAAG,aAAa,WAAW;AAAA,EAC9E;AACA,SAAO,EAAE,GAAG,QAAQ,IAAI;AAC1B;AAEA,eAAsB,WAAW,MAAiC;AAChE,QAAM,MAAM,QAAQ,IAAI;AACxB,QAAM,SAAS,MAAM,WAAW,KAAK,QAAQ,GAAG;AAChD,QAAM,SAAS,eAAe,OAAO,QAAQ,IAAI;AAGjD,QAAM,EAAE,MAAM,IAAI,MAAM,YAAY,EAAE,GAAG,QAAQ,OAAO,GAAG,GAAG;AAE9D,MAAI,KAAK,QAAQ;AACf,gBAAY,QAAQ,OAAO,GAAG;AAC9B;AAAA,EACF;AAGA,QAAM,EAAE,QAAQ,IAAI,MAAM,UAAU;AAAA,IAClC,OAAO;AAAA,IACP,WAAW,OAAO,MAAM;AAAA,IACxB,aAAa,OAAO,MAAM;AAAA,IAC1B;AAAA,EACF,CAAC;AACD,SAAO,MAAM,sBAAsB,OAAO,EAAE;AAG5C,QAAM,SAAS,MAAM,WAAW,QAAQ,GAAG;AAC3C,SAAO,MAAM,iBAAiB,MAAM,EAAE;AAGtC,SAAO,KAAK,yBAAyB,MAAM,MAAM,eAAe;AAChE,QAAM,cAAc,MAAM,YAAY;AAAA,IACpC,MAAM,OAAO;AAAA,IACb;AAAA,IACA,UAAU,OAAO,MAAM;AAAA,EACzB,CAAC;AAGD,QAAM,UAAU,aAAa,QAAQ;AAAA,IACnC,KAAK;AAAA,IACL,WAAW,YAAY;AAAA,EACzB,CAAC;AACD,SAAO,KAAK,YAAY,OAAO,QAAQ,YAAY;AACnD,QAAM,QAAQ,MAAM,WAAW,EAAE,MAAM,OAAO,MAAM,UAAU,OAAO,UAAU,QAAQ,CAAC;AAExF,eAAa,QAAQ,QAAQ,YAAY,gBAAgB,MAAM,QAAQ;AACzE;AAEA,eAAe,WAAW,QAAgB,KAA8B;AACtE,QAAM,EAAE,IAAI,IAAI;AAChB,MAAI,IAAI,WAAW,WAAW;AAC5B,WAAO,IAAI;AAAA,EACb;AACA,MAAI,IAAI,WAAW,UAAU;AAC3B,WAAO,KAAK,kBAAkB,IAAI,IAAI,MAAM;AAC5C,UAAM,SAAS,MAAM,UAAU;AAAA,MAC7B,MAAM,OAAO;AAAA,MACb,UAAUC,OAAK,QAAQ,KAAK,IAAI,IAAI;AAAA,MACpC,UAAU,IAAI;AAAA,IAChB,CAAC;AACD,WAAO,OAAO;AAAA,EAChB;AACA,QAAM,IAAI,kBAAkB,2BAA2B,IAAI,MAAM,EAAE;AACrE;AAEA,SAAS,WAAW,QAAwB;AAC1C,QAAM,EAAE,IAAI,IAAI;AAChB,SAAO,IAAI,WAAW,YAAY,IAAI,SAAS;AACjD;AAEA,SAAS,YAAY,QAAgB,OAAiB,KAAmB;AACvE,SAAO,KAAK,kCAAkC;AAC9C,SAAO,KAAK,EAAE;AACd,SAAO,KAAK,gBAAgB;AAC5B,aAAW,QAAQ,OAAO;AACxB,WAAO,KAAK,KAAK,IAAI,EAAE;AAAA,EACzB;AACA,SAAO,KAAK,EAAE;AAEd,QAAM,EAAE,IAAI,IAAI;AAChB,MAAI,IAAI,WAAW,UAAU;AAC3B,WAAO,KAAK;AAAA,EAAsB,IAAI,IAAI,EAAE;AAAA,EAC9C,WAAW,IAAI,WAAW,WAAW;AACnC,WAAO,KAAK;AAAA,EAAmB,IAAI,MAAM,EAAE;AAAA,EAC7C;AACA,SAAO,KAAK,EAAE;AAEd,SAAO,KAAK;AAAA,OAAqB,UAAU,OAAO,QAAQ,CAAC,EAAE;AAC7D,SAAO,KAAK,EAAE;AAEd,QAAM,UAAU,aAAa,QAAQ;AAAA,IACnC,KAAK,WAAW,MAAM;AAAA,IACtB,WAAW;AAAA,EACb,CAAC;AACD,SAAO,KAAK,UAAU;AACtB,SAAO,KAAK,KAAK,UAAU,SAAS,MAAM,CAAC,CAAC;AAC9C;AAEA,SAAS,aACP,QACA,QACA,cACA,SACM;AACN,SAAO,KAAK,EAAE;AACd,SAAO,KAAK,0BAA0B;AACtC,SAAO,KAAK,EAAE;AACd,SAAO,KAAK,YAAY,OAAO,IAAI,OAAO,EAAE;AAC5C,SAAO,KAAK,aAAa,OAAO,QAAQ,EAAE;AAC1C,SAAO,KAAK,UAAU;AACtB,aAAW,UAAU,OAAO,IAAI,SAAS;AACvC,WAAO,KAAK,KAAK,MAAM,EAAE;AAAA,EAC3B;AACA,SAAO,KAAK,EAAE;AACd,SAAO,KAAK,QAAQ,MAAM,EAAE;AAC5B,SAAO,KAAK,eAAe,YAAY,EAAE;AACzC,SAAO,KAAK,aAAa,OAAO,EAAE;AACpC;;;AKrJA,OAAOC,YAAU;AAWjB,eAAsB,iBAAiB,MAAoC;AACzE,QAAM,MAAM,QAAQ,IAAI;AACxB,QAAM,EAAE,OAAO,IAAI,MAAM,WAAW,KAAK,QAAQ,GAAG;AACpD,QAAM,YAAY,QAAQ,GAAG;AAE7B,MAAI,OAAO,IAAI,WAAW,UAAU;AAClC,UAAM,IAAI;AAAA,MACR,sDAAsD,OAAO,IAAI,MAAM;AAAA,IACzE;AAAA,EACF;AAEA,QAAM,WAAWC,OAAK,QAAQ,KAAK,OAAO,IAAI,IAAI;AAClD,SAAO,KAAK,kBAAkB,OAAO,IAAI,IAAI,MAAM;AAEnD,QAAM,SAAS,MAAM,UAAU;AAAA,IAC7B,MAAM,OAAO;AAAA,IACb;AAAA,IACA,UAAU,OAAO,IAAI;AAAA,EACvB,CAAC;AAED,SAAO,KAAK,eAAe;AAC3B,SAAO,KAAK,YAAY,OAAO,OAAO,EAAE;AACxC,MAAI,OAAO,UAAW,QAAO,KAAK,cAAc,OAAO,SAAS,EAAE;AAClE,MAAI,OAAO,OAAQ,QAAO,KAAK,YAAY,OAAO,MAAM,EAAE;AAC5D;;;AC5BA,eAAsB,mBAAmB,MAAoC;AAC3E,QAAM,MAAM,QAAQ,IAAI;AACxB,QAAM,SAAS,MAAM,WAAW,KAAK,QAAQ,GAAG;AAChD,QAAM,EAAE,OAAO,IAAI;AACnB,QAAM,EAAE,MAAM,IAAI,MAAM,YAAY,QAAQ,GAAG;AAE/C,QAAM,EAAE,QAAQ,IAAI,MAAM,UAAU;AAAA,IAClC,OAAO;AAAA,IACP,WAAW,OAAO,MAAM;AAAA,IACxB,aAAa,OAAO,MAAM;AAAA,IAC1B;AAAA,EACF,CAAC;AAED,SAAO,KAAK,yBAAyB,MAAM,MAAM,eAAe;AAEhE,QAAM,SAAS,MAAM,YAAY;AAAA,IAC/B,MAAM,OAAO;AAAA,IACb;AAAA,IACA,UAAU,OAAO,MAAM;AAAA,EACzB,CAAC;AAED,SAAO,KAAK,sBAAsB;AAClC,SAAO,KAAK,mBAAmB,OAAO,cAAc,EAAE;AACtD,MAAI,OAAO,UAAW,QAAO,KAAK,cAAc,OAAO,SAAS,EAAE;AAClE,MAAI,OAAO,OAAQ,QAAO,KAAK,YAAY,OAAO,MAAM,EAAE;AAC5D;;;ApBtBA,IAAM,UAAU,IAAI,QAAQ;AAG5B,SAAS,QAAQ,OAAe,UAA8B;AAC5D,SAAO,CAAC,GAAG,UAAU,KAAK;AAC5B;AAOA,SAAS,OACP,IAC4B;AAC5B,SAAO,OAAO,YAAe;AAC3B,UAAM,OAAO,EAAE,GAAG,QAAQ,KAAK,GAAG,GAAG,QAAQ;AAC7C,QAAI,KAAK,MAAO,UAAS,IAAI;AAC7B,QAAI;AACF,YAAM,GAAG,IAAI;AAAA,IACf,SAAS,KAAK;AACZ,UAAI,oBAAoB,GAAG,GAAG;AAC5B,eAAO,MAAM,IAAI,OAAO;AACxB,mBAAW,QAAQ,IAAI,WAAW,CAAC,GAAG;AACpC,iBAAO,MAAM,KAAK,IAAI,EAAE;AAAA,QAC1B;AAAA,MACF,OAAO;AACL,eAAO,MAAM,eAAe,QAAS,IAAI,SAAS,IAAI,UAAW,OAAO,GAAG,CAAC;AAAA,MAC9E;AACA,cAAQ,WAAW;AAAA,IACrB;AAAA,EACF;AACF;AAEA,QACG,KAAK,cAAc,EACnB,YAAY,2EAA2E,EACvF,QAAQ,OAAO,EACf,OAAO,uBAAuB,yBAAyB,EACvD,OAAO,WAAW,sBAAsB;AAE3C,QACG,QAAQ,MAAM,EACd,YAAY,mCAAmC,EAC/C,OAAO,aAAa,4BAA4B,EAChD,OAAO,SAAS,wBAAwB,EACxC,OAAO,WAAW,8BAA8B,EAChD,OAAO,OAAO,WAAW,CAAC;AAE7B,QACG,QAAQ,UAAU,EAClB,YAAY,kDAAkD,EAC9D,OAAO,OAAO,eAAe,CAAC;AAEjC,QACG,QAAQ,SAAS,EACjB,YAAY,2DAA2D,EACvE,OAAO,OAAO,cAAc,CAAC;AAEhC,QACG,QAAQ,YAAY,EACpB,YAAY,wDAAwD,EACpE,OAAO,OAAO,gBAAgB,CAAC;AAElC,QACG,QAAQ,cAAc,EACtB,YAAY,2CAA2C,EACvD,OAAO,OAAO,kBAAkB,CAAC;AAEpC,QACG,QAAQ,KAAK,EACb,YAAY,0DAA0D,EACtE,OAAO,aAAa,yDAAyD,EAC7E,OAAO,mBAAmB,qCAAqC,SAAS,CAAC,CAAC,EAC1E,OAAO,oBAAoB,qCAAqC,SAAS,CAAC,CAAC,EAC3E,OAAO,OAAO,UAAU,CAAC;AAE5B,QAAQ,WAAW,QAAQ,IAAI,EAAE,MAAM,CAAC,QAAQ;AAC9C,SAAO,MAAM,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAC7D,UAAQ,WAAW;AACrB,CAAC;","names":["path","path","path","path","path","path","path","path","path","path","path","path","path","path","path","path","path","path","openAsBlob","path","FormData","FormData","openAsBlob","path","path","path","path"]}
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts","../src/commands/init.ts","../src/utils/errors.ts","../src/utils/fs.ts","../src/utils/logger.ts","../src/commands/package.ts","../src/config/loadConfig.ts","../src/config/resolveEnv.ts","../src/config/schema.ts","../src/suite/createZip.ts","../src/utils/paths.ts","../src/commands/validate.ts","../src/suite/discoverFlows.ts","../src/suite/validateSuite.ts","../src/commands/run.ts","../src/browserstack/uploadApp.ts","../src/browserstack/client.ts","../src/browserstack/uploadSuite.ts","../src/browserstack/startBuild.ts","../src/browserstack/buildStatus.ts","../src/commands/uploadApp.ts","../src/commands/uploadSuite.ts"],"sourcesContent":["import { Command } from \"commander\";\nimport { initCommand } from \"./commands/init.js\";\nimport { packageCommand } from \"./commands/package.js\";\nimport { runCommand } from \"./commands/run.js\";\nimport { uploadAppCommand } from \"./commands/uploadApp.js\";\nimport { uploadSuiteCommand } from \"./commands/uploadSuite.js\";\nimport { validateCommand } from \"./commands/validate.js\";\nimport { isMaestroStackError } from \"./utils/errors.js\";\nimport { logger, setDebug } from \"./utils/logger.js\";\n\nconst program = new Command();\n\n/** Accumulate repeated CLI options (e.g. --device a --device b) into an array. */\nfunction collect(value: string, previous: string[]): string[] {\n return [...previous, value];\n}\n\n/**\n * Wrap a command action: merge program-level (global) options, apply debug mode,\n * and turn expected errors into a clean message with exit code 1 instead of an\n * unhandled rejection / stack dump.\n */\nfunction action<T extends object>(\n fn: (opts: T & { config?: string; debug?: boolean }) => Promise<void>,\n): (opts: T) => Promise<void> {\n return async (rawOpts: T) => {\n const opts = { ...program.opts(), ...rawOpts };\n if (opts.debug) setDebug(true);\n try {\n await fn(opts);\n } catch (err) {\n if (isMaestroStackError(err)) {\n logger.error(err.message);\n for (const line of err.details ?? []) {\n logger.error(` ${line}`);\n }\n } else {\n logger.error(err instanceof Error ? (err.stack ?? err.message) : String(err));\n }\n process.exitCode = 1;\n }\n };\n}\n\nprogram\n .name(\"maestrostack\")\n .description(\"Config-driven CLI for running Maestro tests on BrowserStack App Automate.\")\n .version(\"0.1.0\")\n .option(\"-c, --config <path>\", \"path to the config file\")\n .option(\"--debug\", \"enable debug logging\");\n\nprogram\n .command(\"init\")\n .description(\"Create a starter maestrostack.yml\")\n .option(\"--android\", \"scaffold an Android config\")\n .option(\"--ios\", \"scaffold an iOS config\")\n .option(\"--force\", \"overwrite an existing config\")\n .action(action(initCommand));\n\nprogram\n .command(\"validate\")\n .description(\"Validate the config, suite structure and devices\")\n .action(action(validateCommand));\n\nprogram\n .command(\"package\")\n .description(\"Discover flows and create the suite zip without uploading\")\n .action(action(packageCommand));\n\nprogram\n .command(\"upload-app\")\n .description(\"Upload only the app and print its BrowserStack app_url\")\n .action(action(uploadAppCommand));\n\nprogram\n .command(\"upload-suite\")\n .description(\"Package and upload only the Maestro suite\")\n .action(action(uploadSuiteCommand));\n\nprogram\n .command(\"run\")\n .description(\"Package, upload and trigger a BrowserStack Maestro build\")\n .option(\"--dry-run\", \"validate and print the payload without making API calls\")\n .option(\"--device <name>\", \"override run.devices (repeatable)\", collect, [])\n .option(\"--execute <path>\", \"override run.execute (repeatable)\", collect, [])\n .option(\"--max-parallel <n>\", \"run at most N listed devices at once (sequential batches)\", (v) => parseInt(v, 10))\n .action(action(runCommand));\n\nprogram.parseAsync(process.argv).catch((err) => {\n logger.error(err instanceof Error ? err.message : String(err));\n process.exitCode = 1;\n});\n","import { writeFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport type { Platform } from \"../config/schema.js\";\nimport { MaestroStackError } from \"../utils/errors.js\";\nimport { pathExists } from \"../utils/fs.js\";\nimport { logger } from \"../utils/logger.js\";\n\nexport interface InitOptions {\n android?: boolean;\n ios?: boolean;\n force?: boolean;\n}\n\nconst CONFIG_NAME = \"maestrostack.yml\";\n\n/** Create a starter maestrostack.yml. */\nexport async function initCommand(opts: InitOptions): Promise<void> {\n if (opts.android && opts.ios) {\n throw new MaestroStackError(\"Pass only one of --android or --ios.\");\n }\n const platform: Platform = opts.ios ? \"ios\" : \"android\";\n\n const cwd = process.cwd();\n const target = path.join(cwd, CONFIG_NAME);\n\n if ((await pathExists(target)) && !opts.force) {\n throw new MaestroStackError(`${CONFIG_NAME} already exists.`, [\n \"Use --force to overwrite it.\",\n ]);\n }\n\n await writeFile(target, template(platform), \"utf8\");\n logger.info(`Created ${CONFIG_NAME} (platform: ${platform}).`);\n logger.info(\"Next: set BROWSERSTACK_USERNAME / BROWSERSTACK_ACCESS_KEY, then run `maestrostack validate`.\");\n}\n\nfunction template(platform: Platform): string {\n const appPath = platform === \"ios\" ? \"./apps/app-release.ipa\" : \"./apps/app-release.apk\";\n const devices =\n platform === \"ios\"\n ? [\"iPhone 15-17.0\"]\n : [\"Samsung Galaxy S20-10.0\", \"Google Pixel 7-13.0\"];\n\n const deviceLines = devices.map((d) => ` - ${d}`).join(\"\\n\");\n\n // Always \\n line endings so the file is identical across platforms.\n return [\n \"version: 1\",\n \"\",\n \"auth:\",\n \" username: ${BROWSERSTACK_USERNAME}\",\n \" accessKey: ${BROWSERSTACK_ACCESS_KEY}\",\n \"\",\n `platform: ${platform}`,\n \"\",\n \"app:\",\n \" source: upload\",\n ` path: ${appPath}`,\n \" customId: SampleApp\",\n \"\",\n \"suite:\",\n \" root: .\",\n \" packageName: Flows.zip\",\n \" customId: SampleTest\",\n \" include:\",\n \" - smoke/**/*.yml\",\n \" - regression/**/*.yml\",\n \" exclude:\",\n \" - apps/**\",\n \"\",\n \"run:\",\n \" project: Maestro_Test\",\n \" devices:\",\n deviceLines,\n \" executeMode: explicit\",\n \" execute:\",\n \" - smoke/login.yml\",\n \" options:\",\n \" networkLogs: true\",\n \" deviceLogs: true\",\n \"\",\n ].join(\"\\n\");\n}\n","/**\n * Error type for all expected, user-facing failures (bad config, missing files,\n * API errors, etc). The CLI entrypoint catches these and prints a clean message\n * with exit code 1, rather than dumping a stack trace.\n */\nexport class MaestroStackError extends Error {\n /** Optional extra lines shown beneath the main message (e.g. hints). */\n readonly details?: string[];\n\n constructor(message: string, details?: string[]) {\n super(message);\n this.name = \"MaestroStackError\";\n this.details = details;\n }\n}\n\n/** Type guard for {@link MaestroStackError}. */\nexport function isMaestroStackError(err: unknown): err is MaestroStackError {\n return err instanceof MaestroStackError;\n}\n","import { constants } from \"node:fs\";\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\n\n/** Returns true if a file or directory exists at `p`. */\nexport async function pathExists(p: string): Promise<boolean> {\n try {\n await fs.access(p, constants.F_OK);\n return true;\n } catch {\n return false;\n }\n}\n\n/** Returns true if `p` exists and is a regular file. */\nexport async function isFile(p: string): Promise<boolean> {\n try {\n const stat = await fs.stat(p);\n return stat.isFile();\n } catch {\n return false;\n }\n}\n\n/** Returns true if `p` exists and is a directory. */\nexport async function isDirectory(p: string): Promise<boolean> {\n try {\n const stat = await fs.stat(p);\n return stat.isDirectory();\n } catch {\n return false;\n }\n}\n\n/** Creates a directory (and parents) if it does not already exist. */\nexport async function ensureDir(dir: string): Promise<void> {\n await fs.mkdir(dir, { recursive: true });\n}\n\n/** Removes a directory tree if present; no-op if missing. */\nexport async function removeDir(dir: string): Promise<void> {\n await fs.rm(dir, { recursive: true, force: true });\n}\n\n/** Lower-cased file extension including the dot (e.g. \".apk\"). */\nexport function extname(p: string): string {\n return path.extname(p).toLowerCase();\n}\n","/* eslint-disable no-console */\n\nlet debugEnabled = false;\n\n/** Enable or disable debug-level output (wired to the global --debug flag). */\nexport function setDebug(enabled: boolean): void {\n debugEnabled = enabled;\n}\n\nexport function isDebugEnabled(): boolean {\n return debugEnabled;\n}\n\nexport const logger = {\n /** Primary user-facing output. */\n info(message = \"\"): void {\n console.log(message);\n },\n warn(message: string): void {\n console.warn(`warning: ${message}`);\n },\n error(message: string): void {\n console.error(message);\n },\n /** Only printed when --debug is set. Prefixed so it is visually distinct. */\n debug(message: string): void {\n if (debugEnabled) {\n console.error(`[debug] ${message}`);\n }\n },\n};\n\n/**\n * Mask a secret for display, keeping only the last few characters so logs are\n * still useful for debugging without exposing the value. Empty/short values are\n * fully masked.\n */\nexport function redact(secret: string | undefined): string {\n if (!secret) return \"\";\n if (secret.length <= 4) return \"****\";\n return `****${secret.slice(-4)}`;\n}\n","import path from \"node:path\";\nimport { loadConfig } from \"../config/loadConfig.js\";\nimport { createZip } from \"../suite/createZip.js\";\nimport { logger } from \"../utils/logger.js\";\nimport { validateAll, type GlobalOptions } from \"./validate.js\";\n\n/**\n * Discover flows and create the suite zip without uploading. Useful for\n * debugging the resulting archive structure.\n */\nexport async function packageCommand(opts: GlobalOptions): Promise<void> {\n const cwd = process.cwd();\n const loaded = await loadConfig(opts.config, cwd);\n const { config } = loaded;\n const { flows } = await validateAll(loaded, cwd);\n\n const { zipPath } = await createZip({\n files: flows,\n suiteRoot: config.suite.root,\n packageName: config.suite.packageName,\n cwd,\n });\n\n logger.info(`Created suite zip: ${path.relative(cwd, zipPath) || zipPath}`);\n logger.info(\"Included flows:\");\n for (const flow of flows) {\n logger.info(`- ${flow}`);\n }\n}\n","import { readFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport dotenv from \"dotenv\";\nimport { parse as parseYaml } from \"yaml\";\nimport { MaestroStackError } from \"../utils/errors.js\";\nimport { isFile } from \"../utils/fs.js\";\nimport { logger } from \"../utils/logger.js\";\nimport { resolveEnv } from \"./resolveEnv.js\";\nimport { configSchema, type Config } from \"./schema.js\";\n\nconst DEFAULT_CONFIG_NAMES = [\"maestrostack.yml\", \"maestrostack.yaml\"];\n\nexport interface LoadedConfig {\n config: Config;\n /** Absolute path to the resolved config file. */\n configPath: string;\n}\n\n/**\n * Resolve which config file to use. An explicit `-c` path must exist; otherwise\n * we look for maestrostack.yml then maestrostack.yaml in the working directory.\n */\nexport async function resolveConfigPath(\n explicit: string | undefined,\n cwd: string = process.cwd(),\n): Promise<string> {\n if (explicit) {\n const resolved = path.resolve(cwd, explicit);\n if (!(await isFile(resolved))) {\n throw new MaestroStackError(`Config file not found: ${explicit}`);\n }\n return resolved;\n }\n\n for (const name of DEFAULT_CONFIG_NAMES) {\n const candidate = path.resolve(cwd, name);\n if (await isFile(candidate)) {\n return candidate;\n }\n }\n\n throw new MaestroStackError(\"maestrostack.yml not found.\", [\n \"Run `maestrostack init` to create one, or pass --config <path>.\",\n ]);\n}\n\n/**\n * Load, env-substitute, parse and validate a config file. The `.env` in the\n * working directory is loaded first so `${VAR}` tokens can resolve against it.\n */\nexport async function loadConfig(\n explicit: string | undefined,\n cwd: string = process.cwd(),\n): Promise<LoadedConfig> {\n // Load .env (does not override already-set process.env values).\n dotenv.config({ path: path.resolve(cwd, \".env\") });\n\n const configPath = await resolveConfigPath(explicit, cwd);\n logger.debug(`Using config: ${configPath}`);\n\n const raw = await readFile(configPath, \"utf8\");\n const substituted = resolveEnv(raw);\n\n let parsed: unknown;\n try {\n parsed = parseYaml(substituted);\n } catch (err) {\n const reason = err instanceof Error ? err.message : String(err);\n throw new MaestroStackError(`Invalid YAML in ${path.basename(configPath)}.`, [reason]);\n }\n\n const result = configSchema.safeParse(parsed);\n if (!result.success) {\n const details = result.error.issues.map((issue) => {\n const where = issue.path.length > 0 ? `${issue.path.join(\".\")}: ` : \"\";\n return `- ${where}${issue.message}`;\n });\n throw new MaestroStackError(\"Invalid configuration:\", details);\n }\n\n return { config: result.data, configPath };\n}\n","import { MaestroStackError } from \"../utils/errors.js\";\n\n/** Matches ${VAR_NAME} tokens. Variable names follow shell-ish conventions. */\nconst ENV_TOKEN = /\\$\\{([A-Za-z_][A-Za-z0-9_]*)\\}/g;\n\n/**\n * Substitute `${VAR}` tokens in the raw config text using `env` (defaults to\n * process.env). All missing variables are collected and reported together so the\n * user fixes them in one pass - and crucially this happens before any upload.\n */\nexport function resolveEnv(\n raw: string,\n env: NodeJS.ProcessEnv = process.env,\n): string {\n const missing = new Set<string>();\n\n const resolved = raw.replace(ENV_TOKEN, (_match, name: string) => {\n const value = env[name];\n if (value === undefined || value === \"\") {\n missing.add(name);\n return \"\";\n }\n return value;\n });\n\n if (missing.size > 0) {\n const names = [...missing].sort();\n throw new MaestroStackError(\n `Missing required environment variable(s): ${names.join(\", \")}`,\n [\"Set them in your shell or a .env file in the working directory.\"],\n );\n }\n\n return resolved;\n}\n","import { z } from \"zod\";\n\n/**\n * Zod schema for maestrostack.yml. Parsing yields a fully-typed {@link Config}.\n * Validation messages here are intentionally human-readable since they surface\n * directly in the CLI.\n */\n\nconst nonEmpty = (label: string) =>\n z.string({ required_error: `${label} is required` }).trim().min(1, `${label} must not be empty`);\n\nconst authSchema = z.object({\n username: nonEmpty(\"BrowserStack username (auth.username)\"),\n accessKey: nonEmpty(\"BrowserStack access key (auth.accessKey)\"),\n});\n\nconst appUploadSchema = z.object({\n source: z.literal(\"upload\"),\n path: nonEmpty(\"app.path\"),\n customId: z.string().trim().min(1).optional(),\n});\n\nconst appUrlSchema = z.object({\n source: z.literal(\"app_url\"),\n appUrl: nonEmpty(\"app.appUrl\").refine((v) => v.startsWith(\"bs://\"), {\n message: \"app.appUrl must start with bs://\",\n }),\n customId: z.string().trim().min(1).optional(),\n});\n\nconst appCustomIdSchema = z.object({\n // Present in the schema so configs validate, but resolution is deferred (see validate.ts).\n source: z.literal(\"custom_id\"),\n customId: nonEmpty(\"app.customId\"),\n});\n\nconst appSchema = z.discriminatedUnion(\"source\", [\n appUploadSchema,\n appUrlSchema,\n appCustomIdSchema,\n]);\n\nconst suiteSchema = z.object({\n root: z.string().trim().min(1).default(\".\"),\n packageName: z.string().trim().min(1).default(\"Flows.zip\"),\n customId: z.string().trim().min(1).optional(),\n include: z.array(nonEmpty(\"suite.include entry\")).default([\"**/*.yml\", \"**/*.yaml\"]),\n exclude: z.array(z.string()).default([]),\n});\n\nconst optionsSchema = z\n .object({\n networkLogs: z.boolean().optional(),\n deviceLogs: z.boolean().optional(),\n })\n .default({});\n\nconst runSchema = z.object({\n project: nonEmpty(\"run.project\"),\n devices: z\n .array(nonEmpty(\"device\"))\n .min(1, \"run.devices must contain at least one device\"),\n executeMode: z.enum([\"explicit\", \"main\"]).default(\"explicit\"),\n execute: z.array(nonEmpty(\"run.execute entry\")).optional(),\n maxParallel: z\n .number({ invalid_type_error: \"run.maxParallel must be a number\" })\n .int(\"run.maxParallel must be an integer\")\n .positive(\"run.maxParallel must be a positive integer\")\n .optional(),\n options: optionsSchema,\n});\n\nexport const configSchema = z.object({\n version: z.literal(1, {\n errorMap: () => ({ message: \"Unsupported config version. Expected version: 1\" }),\n }),\n auth: authSchema,\n platform: z.enum([\"android\", \"ios\"], {\n errorMap: () => ({ message: \"Unsupported platform. Expected android or ios\" }),\n }),\n app: appSchema,\n suite: suiteSchema.default({}),\n run: runSchema,\n});\n\nexport type Config = z.infer<typeof configSchema>;\nexport type AppConfig = Config[\"app\"];\nexport type Platform = Config[\"platform\"];\n","import { createWriteStream } from \"node:fs\";\nimport path from \"node:path\";\nimport archiver from \"archiver\";\nimport { ensureDir } from \"../utils/fs.js\";\nimport { distDir } from \"../utils/paths.js\";\n\nexport interface ZipResult {\n /** Absolute path to the written zip file. */\n zipPath: string;\n /** Forward-slash flow paths that were included (relative to suite root). */\n files: string[];\n}\n\n/** Top-level folder inside the archive. BrowserStack locates flows under this. */\nexport const SUITE_PREFIX = \"Flows\";\n\n/**\n * Create the suite zip from the given flow files. Each entry is stored under\n * `Flows/<relativePath>` using forward slashes (required by the ZIP spec and so\n * BrowserStack can locate flows regardless of the host OS that built the zip).\n */\nexport async function createZip(options: {\n files: string[];\n suiteRoot: string;\n packageName: string;\n cwd?: string;\n}): Promise<ZipResult> {\n const cwd = options.cwd ?? process.cwd();\n const root = path.resolve(cwd, options.suiteRoot);\n const outDir = distDir(cwd);\n await ensureDir(outDir);\n const zipPath = path.join(outDir, options.packageName);\n\n await new Promise<void>((resolve, reject) => {\n const output = createWriteStream(zipPath);\n const archive = archiver(\"zip\", { zlib: { level: 9 } });\n\n output.on(\"close\", () => resolve());\n output.on(\"error\", reject);\n archive.on(\"error\", reject);\n archive.on(\"warning\", (err) => {\n if (err.code === \"ENOENT\") return; // non-fatal\n reject(err);\n });\n\n archive.pipe(output);\n\n for (const rel of options.files) {\n // `rel` is already forward-slash (from discoverFlows); resolve to the OS\n // path for reading, but name the entry with forward slashes.\n const absolute = path.join(root, ...rel.split(\"/\"));\n const entryName = `${SUITE_PREFIX}/${rel}`;\n archive.file(absolute, { name: entryName });\n }\n\n void archive.finalize();\n });\n\n return { zipPath, files: options.files };\n}\n","import path from \"node:path\";\n\n/**\n * Internal working directory MaestroStack writes to. Relative to the current\n * working directory so it lands next to the user's project. Should be gitignored.\n */\nexport const WORK_DIR = \".maestrostack\";\n\n/** Directory for build artifacts (the generated suite zip). */\nexport function distDir(cwd: string = process.cwd()): string {\n return path.join(cwd, WORK_DIR, \"dist\");\n}\n\n/** Directory for transient files. */\nexport function tmpDir(cwd: string = process.cwd()): string {\n return path.join(cwd, WORK_DIR, \"tmp\");\n}\n\n/**\n * Convert a path into the forward-slash form used internally and by BrowserStack\n * (zip entries, execute paths). Both separators are normalized so a config or\n * `--execute` value authored on Windows (`smoke\\login.yml`) matches discovered\n * flows regardless of the OS MaestroStack runs on.\n */\nexport function toPosix(p: string): string {\n return p.split(/[\\\\/]+/).join(\"/\");\n}\n","import path from \"node:path\";\nimport type { Config } from \"../config/schema.js\";\nimport { loadConfig, type LoadedConfig } from \"../config/loadConfig.js\";\nimport { discoverFlows } from \"../suite/discoverFlows.js\";\nimport { validateSuite } from \"../suite/validateSuite.js\";\nimport { MaestroStackError } from \"../utils/errors.js\";\nimport { extname, isFile } from \"../utils/fs.js\";\nimport { logger } from \"../utils/logger.js\";\n\nexport interface GlobalOptions {\n config?: string;\n debug?: boolean;\n}\n\nconst PLATFORM_EXT: Record<Config[\"platform\"], string> = {\n android: \".apk\",\n ios: \".ipa\",\n};\n\n/**\n * Semantic validation of the app block beyond the Zod schema: file existence and\n * platform/extension agreement. (app_url and custom_id reference an existing\n * BrowserStack app, so there is nothing local to check.)\n */\nexport async function validateApp(config: Config, cwd: string): Promise<void> {\n const { app, platform } = config;\n\n if (app.source === \"upload\") {\n const appPath = path.resolve(cwd, app.path);\n if (!(await isFile(appPath))) {\n throw new MaestroStackError(`app.path does not exist: ${app.path}`);\n }\n const expected = PLATFORM_EXT[platform];\n if (extname(appPath) !== expected) {\n throw new MaestroStackError(\n `Platform \"${platform}\" requires a ${expected} file, but app.path is \"${app.path}\".`,\n );\n }\n }\n}\n\n/**\n * Full validation pipeline shared by `validate` and `run`: app block, flow\n * discovery and suite/execute checks. Returns the discovered flow paths.\n */\nexport async function validateAll(\n loaded: LoadedConfig,\n cwd: string,\n): Promise<{ flows: string[] }> {\n const { config, configPath } = loaded;\n await validateApp(config, cwd);\n\n const flows = await discoverFlows(config.suite, {\n cwd,\n configBasename: path.basename(configPath),\n });\n\n await validateSuite(config, flows, cwd);\n\n return { flows };\n}\n\nexport async function validateCommand(opts: GlobalOptions): Promise<void> {\n const cwd = process.cwd();\n const loaded = await loadConfig(opts.config, cwd);\n const { flows } = await validateAll(loaded, cwd);\n const { config } = loaded;\n\n logger.info(\"Configuration is valid.\");\n logger.info(\"\");\n logger.info(`Config: ${path.relative(cwd, loaded.configPath) || loaded.configPath}`);\n logger.info(`Platform: ${config.platform}`);\n logger.info(`Project: ${config.run.project}`);\n logger.info(`App: ${describeApp(config)}`);\n logger.info(`Devices: ${config.run.devices.length}`);\n for (const device of config.run.devices) {\n logger.info(` - ${device}`);\n }\n logger.info(`Flows: ${flows.length}`);\n for (const flow of flows) {\n logger.info(` - ${flow}`);\n }\n logger.info(`Execute: ${config.run.executeMode}`);\n}\n\nfunction describeApp(config: Config): string {\n const { app } = config;\n if (app.source === \"upload\") return `upload ${app.path}`;\n if (app.source === \"app_url\") return `app_url ${app.appUrl}`;\n return `custom_id ${app.customId}`;\n}\n","import path from \"node:path\";\nimport fg from \"fast-glob\";\nimport type { Config } from \"../config/schema.js\";\nimport { MaestroStackError } from \"../utils/errors.js\";\nimport { isDirectory } from \"../utils/fs.js\";\n\nexport interface DiscoverOptions {\n /** Working directory the suite root is resolved against. */\n cwd?: string;\n /** Basename of the active config file, always excluded from the suite. */\n configBasename?: string;\n}\n\n/**\n * Always-on ignore patterns. Root-level YAML files are treated as config files,\n * not Maestro flows (top-level only - `*.yml` matches `staging.yml` but not\n * `smoke/login.yml`). Build/VCS/dependency dirs are never packaged.\n */\nconst BUILT_IN_IGNORES = [\n \"*.yml\",\n \"*.yaml\",\n \".git/**\",\n \"node_modules/**\",\n \".maestrostack/**\",\n];\n\n/**\n * Discover Maestro flow files under `suite.root` using the configured include /\n * exclude globs. Returns sorted, forward-slash relative paths (fast-glob emits\n * POSIX separators on every OS, keeping output identical across platforms).\n */\nexport async function discoverFlows(\n suite: Config[\"suite\"],\n opts: DiscoverOptions = {},\n): Promise<string[]> {\n const cwd = opts.cwd ?? process.cwd();\n const root = path.resolve(cwd, suite.root);\n\n if (!(await isDirectory(root))) {\n throw new MaestroStackError(`suite.root does not exist: ${suite.root}`);\n }\n\n const ignore = [...BUILT_IN_IGNORES, ...suite.exclude];\n if (opts.configBasename) {\n ignore.push(opts.configBasename);\n }\n\n const matches = await fg(suite.include, {\n cwd: root,\n ignore,\n onlyFiles: true,\n dot: false,\n followSymbolicLinks: false,\n });\n\n if (matches.length === 0) {\n throw new MaestroStackError(\"No Maestro flow files found.\", [\n \"Maestro flow files should live inside subfolders (root-level YAML files are ignored).\",\n `Checked suite.root=\"${suite.root}\" with include=${JSON.stringify(suite.include)}.`,\n ]);\n }\n\n return matches.sort();\n}\n","import path from \"node:path\";\nimport type { Config } from \"../config/schema.js\";\nimport { MaestroStackError } from \"../utils/errors.js\";\nimport { isFile } from \"../utils/fs.js\";\nimport { toPosix } from \"../utils/paths.js\";\n\nexport const MAIN_FLOW = \"main.yaml\";\n\n/**\n * Validate the suite against the discovered flow set and the configured execute\n * mode.\n *\n * - explicit mode: every `run.execute` path must exist in the discovered flows.\n * - main mode: a `main.yaml` must exist at the suite root.\n *\n * `discovered` paths are forward-slash relative to the suite root; execute paths\n * supplied by users are normalized so Windows-style separators still match.\n */\nexport async function validateSuite(\n config: Config,\n discovered: string[],\n cwd: string = process.cwd(),\n): Promise<void> {\n const { run, suite } = config;\n\n if (run.executeMode === \"main\") {\n const mainPath = path.resolve(cwd, suite.root, MAIN_FLOW);\n if (!(await isFile(mainPath))) {\n throw new MaestroStackError(\n `executeMode is \"main\" but ${MAIN_FLOW} was not found in suite.root (${suite.root}).`,\n [`Add a ${MAIN_FLOW} or switch to executeMode: explicit with a run.execute list.`],\n );\n }\n return;\n }\n\n // explicit mode\n if (run.execute && run.execute.length > 0) {\n const available = new Set(discovered.map(toPosix));\n const missing = run.execute\n .map(toPosix)\n .filter((p) => !available.has(p));\n\n if (missing.length > 0) {\n throw new MaestroStackError(\n \"Configured execute file(s) do not exist in the packaged suite:\",\n missing.map((p) => `- ${p}`),\n );\n }\n }\n}\n","import path from \"node:path\";\nimport { loadConfig } from \"../config/loadConfig.js\";\nimport type { Config } from \"../config/schema.js\";\nimport { uploadApp } from \"../browserstack/uploadApp.js\";\nimport { uploadSuite } from \"../browserstack/uploadSuite.js\";\nimport { buildDashboardUrl, buildPath, buildPayload, startBuild } from \"../browserstack/startBuild.js\";\nimport { pollBuild } from \"../browserstack/buildStatus.js\";\nimport { createZip } from \"../suite/createZip.js\";\nimport { MaestroStackError } from \"../utils/errors.js\";\nimport { logger } from \"../utils/logger.js\";\nimport { toPosix } from \"../utils/paths.js\";\nimport { validateAll, type GlobalOptions } from \"./validate.js\";\n\nexport interface RunOptions extends GlobalOptions {\n dryRun?: boolean;\n /** Temporary device override(s); replaces run.devices. */\n device?: string[];\n /** Temporary execute override(s); replaces run.execute (forces explicit mode). */\n execute?: string[];\n /** Temporary max-parallel override; replaces run.maxParallel. */\n maxParallel?: number;\n}\n\nconst APP_PLACEHOLDER = \"<resolved after upload>\";\nconst SUITE_PLACEHOLDER = \"<resolved after upload>\";\n\n/** Apply CLI overrides for devices / execute / maxParallel onto a loaded config. */\nexport function applyOverrides(config: Config, opts: RunOptions): Config {\n let run = config.run;\n if (opts.device && opts.device.length > 0) {\n run = { ...run, devices: opts.device };\n }\n if (opts.execute && opts.execute.length > 0) {\n run = { ...run, execute: opts.execute.map(toPosix), executeMode: \"explicit\" };\n }\n if (opts.maxParallel !== undefined && Number.isFinite(opts.maxParallel)) {\n run = { ...run, maxParallel: opts.maxParallel };\n }\n return { ...config, run };\n}\n\n/**\n * Split the device list into sequential batches. With `maxParallel` set (and\n * smaller than the device count), each batch holds at most `maxParallel` devices\n * so no more than that many run at once. Otherwise there is a single batch with\n * every device.\n */\nexport function deviceBatches(devices: string[], maxParallel?: number): string[][] {\n if (!maxParallel || maxParallel < 1 || maxParallel >= devices.length) {\n return [devices];\n }\n const batches: string[][] = [];\n for (let i = 0; i < devices.length; i += maxParallel) {\n batches.push(devices.slice(i, i + maxParallel));\n }\n return batches;\n}\n\nexport async function runCommand(opts: RunOptions): Promise<void> {\n const cwd = process.cwd();\n const loaded = await loadConfig(opts.config, cwd);\n const config = applyOverrides(loaded.config, opts);\n\n // Validate with overrides applied.\n const { flows } = await validateAll({ ...loaded, config }, cwd);\n\n const batches = deviceBatches(config.run.devices, config.run.maxParallel);\n\n if (opts.dryRun) {\n printDryRun(config, flows, batches);\n return;\n }\n\n // 1. Package the suite.\n const { zipPath } = await createZip({\n files: flows,\n suiteRoot: config.suite.root,\n packageName: config.suite.packageName,\n cwd,\n });\n logger.debug(`Created suite zip: ${zipPath}`);\n\n // 2. Resolve the app (upload local binary or use an existing bs:// url).\n const appUrl = await resolveApp(config, cwd);\n logger.debug(`Resolved app: ${appUrl}`);\n\n // 3. Upload the suite once; every batch reuses it.\n logger.info(`Uploading test suite (${flows.length} flow(s)) ...`);\n const suiteResult = await uploadSuite({\n auth: config.auth,\n zipPath,\n customId: config.suite.customId,\n });\n const testSuiteUrl = suiteResult.test_suite_url;\n\n // 4. Trigger build(s).\n if (batches.length === 1) {\n const payload = buildPayload(config, { app: appUrl, testSuite: testSuiteUrl }, batches[0]);\n logger.info(`Starting ${config.platform} build ...`);\n const build = await startBuild({ auth: config.auth, platform: config.platform, payload });\n printSingleSummary(config, appUrl, testSuiteUrl, build.build_id);\n return;\n }\n\n // Multiple batches: submit one build per batch and wait for it to finish\n // before starting the next, so at most maxParallel devices run at once.\n logger.info(\n `Running ${config.run.devices.length} device(s) in ${batches.length} sequential ` +\n `batch(es) of up to ${config.run.maxParallel}.`,\n );\n logger.info(`App: ${appUrl}`);\n logger.info(`Test suite: ${testSuiteUrl}`);\n\n const results: { buildId: string; status: string; devices: string[] }[] = [];\n for (let i = 0; i < batches.length; i++) {\n const batch = batches[i]!;\n const label = `Batch ${i + 1}/${batches.length}`;\n const payload = buildPayload(config, { app: appUrl, testSuite: testSuiteUrl }, batch);\n logger.info(\"\");\n logger.info(`${label}: starting build on ${batch.join(\", \")} ...`);\n const build = await startBuild({ auth: config.auth, platform: config.platform, payload });\n logger.info(`${label}: build ${build.build_id} running, waiting for it to finish ...`);\n logger.info(`${label}: ${buildDashboardUrl(build.build_id)}`);\n\n const status = await pollBuild({\n auth: config.auth,\n buildId: build.build_id,\n onStatus: (s) => logger.debug(`${label} status: ${s}`),\n });\n logger.info(`${label}: finished with status \"${status}\".`);\n results.push({ buildId: build.build_id, status, devices: batch });\n }\n\n printBatchSummary(config, appUrl, testSuiteUrl, results);\n}\n\nasync function resolveApp(config: Config, cwd: string): Promise<string> {\n const { app } = config;\n if (app.source === \"app_url\") {\n return app.appUrl;\n }\n if (app.source === \"custom_id\") {\n // BrowserStack resolves a custom_id to the latest app uploaded with that id.\n return app.customId;\n }\n if (app.source === \"upload\") {\n logger.info(`Uploading app: ${app.path} ...`);\n const result = await uploadApp({\n auth: config.auth,\n filePath: path.resolve(cwd, app.path),\n customId: app.customId,\n });\n return result.app_url;\n }\n throw new MaestroStackError(\"Unsupported app source.\");\n}\n\nfunction previewApp(config: Config): string {\n const { app } = config;\n if (app.source === \"app_url\") return app.appUrl;\n if (app.source === \"custom_id\") return app.customId;\n return APP_PLACEHOLDER;\n}\n\nfunction printDryRun(config: Config, flows: string[], batches: string[][]): void {\n logger.info(\"Dry run only. No API calls made.\");\n logger.info(\"\");\n logger.info(\"Would package:\");\n for (const flow of flows) {\n logger.info(`- ${flow}`);\n }\n logger.info(\"\");\n\n const { app } = config;\n if (app.source === \"upload\") {\n logger.info(`Would upload app:\\n${app.path}`);\n } else if (app.source === \"app_url\") {\n logger.info(`Would use app:\\n${app.appUrl}`);\n } else if (app.source === \"custom_id\") {\n logger.info(`Would use app (custom_id):\\n${app.customId}`);\n }\n logger.info(\"\");\n\n if (batches.length > 1) {\n logger.info(\n `Would submit ${batches.length} builds sequentially ` +\n `(max ${config.run.maxParallel} device(s) at once), waiting for each to finish:`,\n );\n batches.forEach((batch, i) => {\n logger.info(` Batch ${i + 1}: ${batch.join(\", \")}`);\n });\n } else {\n logger.info(\"Would submit 1 build on all devices.\");\n }\n logger.info(\"\");\n\n logger.info(`Would call:\\nPOST ${buildPath(config.platform)}`);\n logger.info(\"\");\n\n const payload = buildPayload(\n config,\n { app: previewApp(config), testSuite: SUITE_PLACEHOLDER },\n batches[0],\n );\n logger.info(batches.length > 1 ? \"Payload (batch 1):\" : \"Payload:\");\n logger.info(JSON.stringify(payload, null, 2));\n}\n\nfunction printSingleSummary(\n config: Config,\n appUrl: string,\n testSuiteUrl: string,\n buildId: string,\n): void {\n logger.info(\"\");\n logger.info(\"MaestroStack run started\");\n logger.info(\"\");\n logger.info(`Project: ${config.run.project}`);\n logger.info(`Platform: ${config.platform}`);\n logger.info(\"Devices:\");\n for (const device of config.run.devices) {\n logger.info(`- ${device}`);\n }\n logger.info(\"\");\n logger.info(`App: ${appUrl}`);\n logger.info(`Test suite: ${testSuiteUrl}`);\n logger.info(`Build ID: ${buildId}`);\n logger.info(`Build URL: ${buildDashboardUrl(buildId)}`);\n}\n\nfunction printBatchSummary(\n config: Config,\n appUrl: string,\n testSuiteUrl: string,\n results: { buildId: string; status: string; devices: string[] }[],\n): void {\n logger.info(\"\");\n logger.info(\"MaestroStack run complete\");\n logger.info(\"\");\n logger.info(`Project: ${config.run.project}`);\n logger.info(`Platform: ${config.platform}`);\n logger.info(`App: ${appUrl}`);\n logger.info(`Test suite: ${testSuiteUrl}`);\n logger.info(\"\");\n logger.info(\"Builds:\");\n for (const r of results) {\n logger.info(`- ${r.buildId} [${r.status}] on ${r.devices.join(\", \")}`);\n logger.info(` ${buildDashboardUrl(r.buildId)}`);\n }\n}\n","import { openAsBlob } from \"node:fs\";\nimport path from \"node:path\";\nimport { FormData } from \"undici\";\nimport { MaestroStackError } from \"../utils/errors.js\";\nimport { isFile } from \"../utils/fs.js\";\nimport { bsRequest, type BsAuth } from \"./client.js\";\n\nexport interface UploadAppResult {\n app_url: string;\n custom_id?: string;\n expiry?: string;\n app_name?: string;\n}\n\n/**\n * Upload a local app binary (.apk / .ipa) to BrowserStack.\n * POST /app-automate/maestro/v2/app (multipart: file, optional custom_id)\n */\nexport async function uploadApp(options: {\n auth: BsAuth;\n filePath: string;\n customId?: string;\n}): Promise<UploadAppResult> {\n if (!(await isFile(options.filePath))) {\n throw new MaestroStackError(`app.path does not exist: ${options.filePath}`);\n }\n\n const form = new FormData();\n const blob = await openAsBlob(options.filePath);\n form.set(\"file\", blob, path.basename(options.filePath));\n if (options.customId) {\n form.set(\"custom_id\", options.customId);\n }\n\n return bsRequest<UploadAppResult>({\n path: \"/app-automate/maestro/v2/app\",\n method: \"POST\",\n auth: options.auth,\n rawBody: form,\n });\n}\n","import { fetch, type BodyInit } from \"undici\";\nimport { MaestroStackError } from \"../utils/errors.js\";\nimport { logger } from \"../utils/logger.js\";\n\nexport const BASE_URL = \"https://api-cloud.browserstack.com\";\n\nexport interface BsAuth {\n username: string;\n accessKey: string;\n}\n\n/** Build an HTTP Basic auth header from BrowserStack credentials. */\nexport function authHeader(auth: BsAuth): string {\n const token = Buffer.from(`${auth.username}:${auth.accessKey}`).toString(\"base64\");\n return `Basic ${token}`;\n}\n\ninterface RequestOptions {\n path: string;\n method: \"GET\" | \"POST\";\n auth: BsAuth;\n /** JSON body - serialized and sent with application/json. */\n jsonBody?: unknown;\n /** Raw body (e.g. multipart FormData). undici sets the content-type. */\n rawBody?: BodyInit;\n}\n\n/**\n * Perform a request against the BrowserStack API with Basic auth, returning the\n * parsed JSON. Non-2xx responses become a {@link MaestroStackError} carrying the\n * status and any server message - credentials are never logged.\n */\nexport async function bsRequest<T>(options: RequestOptions): Promise<T> {\n const url = `${BASE_URL}${options.path}`;\n const headers: Record<string, string> = {\n authorization: authHeader(options.auth),\n };\n\n let body: BodyInit | undefined;\n if (options.jsonBody !== undefined) {\n headers[\"content-type\"] = \"application/json\";\n body = JSON.stringify(options.jsonBody);\n } else if (options.rawBody !== undefined) {\n body = options.rawBody;\n }\n\n logger.debug(`${options.method} ${url}`);\n\n let response: Awaited<ReturnType<typeof fetch>>;\n try {\n response = await fetch(url, { method: options.method, headers, body });\n } catch (err) {\n const reason = err instanceof Error ? err.message : String(err);\n throw new MaestroStackError(`Failed to reach BrowserStack: ${reason}`);\n }\n\n const text = await response.text();\n let data: unknown = {};\n if (text) {\n try {\n data = JSON.parse(text);\n } catch {\n data = { raw: text };\n }\n }\n\n if (!response.ok) {\n const message = extractErrorMessage(data) ?? response.statusText;\n throw new MaestroStackError(`BrowserStack API error (${response.status}): ${message}`);\n }\n\n return data as T;\n}\n\nfunction extractErrorMessage(data: unknown): string | undefined {\n if (data && typeof data === \"object\") {\n const obj = data as Record<string, unknown>;\n for (const key of [\"message\", \"error\", \"errors\"]) {\n const value = obj[key];\n if (typeof value === \"string\" && value.length > 0) return value;\n }\n }\n return undefined;\n}\n","import { openAsBlob } from \"node:fs\";\nimport path from \"node:path\";\nimport { FormData } from \"undici\";\nimport { MaestroStackError } from \"../utils/errors.js\";\nimport { isFile } from \"../utils/fs.js\";\nimport { bsRequest, type BsAuth } from \"./client.js\";\n\nexport interface UploadSuiteResult {\n test_suite_url: string;\n custom_id?: string;\n expiry?: string;\n test_suite_name?: string;\n framework?: string;\n}\n\n/**\n * Upload a zipped Maestro test suite to BrowserStack.\n * POST /app-automate/maestro/v2/test-suite (multipart: file, optional custom_id)\n */\nexport async function uploadSuite(options: {\n auth: BsAuth;\n zipPath: string;\n customId?: string;\n}): Promise<UploadSuiteResult> {\n if (!(await isFile(options.zipPath))) {\n throw new MaestroStackError(`Suite zip does not exist: ${options.zipPath}`);\n }\n\n const form = new FormData();\n const blob = await openAsBlob(options.zipPath);\n form.set(\"file\", blob, path.basename(options.zipPath));\n if (options.customId) {\n form.set(\"custom_id\", options.customId);\n }\n\n return bsRequest<UploadSuiteResult>({\n path: \"/app-automate/maestro/v2/test-suite\",\n method: \"POST\",\n auth: options.auth,\n rawBody: form,\n });\n}\n","import type { Config, Platform } from \"../config/schema.js\";\nimport { bsRequest, type BsAuth } from \"./client.js\";\n\nexport interface BuildPayload {\n app: string;\n testSuite: string;\n project: string;\n devices: string[];\n execute?: string[];\n networkLogs?: boolean;\n deviceLogs?: boolean;\n}\n\nexport interface StartBuildResult {\n message?: string;\n build_id: string;\n}\n\n/** Build endpoint path for the given platform. */\nexport function buildPath(platform: Platform): string {\n return `/app-automate/maestro/v2/${platform}/build`;\n}\n\n/** Public BrowserStack App Automate dashboard URL for a build. */\nexport function buildDashboardUrl(buildId: string): string {\n return `https://app-automate.browserstack.com/dashboard/v2/builds/${buildId}`;\n}\n\n/**\n * Construct the BrowserStack build payload from config and the resolved app /\n * test-suite references. Pure and side-effect free so it can be unit-tested and\n * reused for the `--dry-run` preview.\n *\n * - `devices` defaults to `run.devices` but can be overridden with a subset, so\n * the runner can submit one build per device batch when `maxParallel` is set.\n * - In explicit mode the flows go into `execute`; in main mode BrowserStack runs\n * the suite's main.yaml.\n * - `networkLogs` / `deviceLogs` are JSON booleans, included only when set.\n */\nexport function buildPayload(\n config: Config,\n resolved: { app: string; testSuite: string },\n devices: string[] = config.run.devices,\n): BuildPayload {\n const { run } = config;\n const payload: BuildPayload = {\n app: resolved.app,\n testSuite: resolved.testSuite,\n project: run.project,\n devices,\n };\n\n if (run.executeMode === \"explicit\" && run.execute && run.execute.length > 0) {\n payload.execute = run.execute;\n }\n\n if (run.options.networkLogs !== undefined) {\n payload.networkLogs = run.options.networkLogs;\n }\n if (run.options.deviceLogs !== undefined) {\n payload.deviceLogs = run.options.deviceLogs;\n }\n\n return payload;\n}\n\n/** Trigger a Maestro build and return the BrowserStack build id. */\nexport async function startBuild(options: {\n auth: BsAuth;\n platform: Platform;\n payload: BuildPayload;\n}): Promise<StartBuildResult> {\n return bsRequest<StartBuildResult>({\n path: buildPath(options.platform),\n method: \"POST\",\n auth: options.auth,\n jsonBody: options.payload,\n });\n}\n","import { MaestroStackError } from \"../utils/errors.js\";\nimport { bsRequest, type BsAuth } from \"./client.js\";\n\nexport interface BuildStatus {\n status: string;\n [key: string]: unknown;\n}\n\n/** Statuses that mean the build is not finished yet. Anything else is terminal. */\nconst IN_PROGRESS = new Set([\"running\", \"queued\", \"creating\"]);\n\n/** Fetch the current status of a build. GET /app-automate/maestro/v2/builds/{id} */\nexport async function getBuild(options: {\n auth: BsAuth;\n buildId: string;\n}): Promise<BuildStatus> {\n return bsRequest<BuildStatus>({\n path: `/app-automate/maestro/v2/builds/${options.buildId}`,\n method: \"GET\",\n auth: options.auth,\n });\n}\n\nconst sleep = (ms: number): Promise<void> => new Promise((r) => setTimeout(r, ms));\n\n/**\n * Poll a build until it reaches a terminal status (anything other than\n * running/queued/creating). Returns the final status string. Throws if the\n * timeout is exceeded.\n */\nexport async function pollBuild(options: {\n auth: BsAuth;\n buildId: string;\n intervalMs?: number;\n timeoutMs?: number;\n onStatus?: (status: string) => void;\n}): Promise<string> {\n const intervalMs = options.intervalMs ?? 20_000;\n const timeoutMs = options.timeoutMs ?? 60 * 60 * 1000; // 1 hour\n const deadline = Date.now() + timeoutMs;\n\n for (;;) {\n const build = await getBuild({ auth: options.auth, buildId: options.buildId });\n const status = build.status;\n options.onStatus?.(status);\n\n if (!IN_PROGRESS.has(status)) {\n return status;\n }\n if (Date.now() >= deadline) {\n throw new MaestroStackError(\n `Timed out waiting for build ${options.buildId} (last status: ${status}).`,\n );\n }\n await sleep(intervalMs);\n }\n}\n","import path from \"node:path\";\nimport { loadConfig } from \"../config/loadConfig.js\";\nimport { uploadApp } from \"../browserstack/uploadApp.js\";\nimport { MaestroStackError } from \"../utils/errors.js\";\nimport { logger } from \"../utils/logger.js\";\nimport { validateApp, type GlobalOptions } from \"./validate.js\";\n\n/**\n * Upload only the app and print the resulting BrowserStack app_url. Only valid\n * for app.source: upload.\n */\nexport async function uploadAppCommand(opts: GlobalOptions): Promise<void> {\n const cwd = process.cwd();\n const { config } = await loadConfig(opts.config, cwd);\n await validateApp(config, cwd);\n\n if (config.app.source !== \"upload\") {\n throw new MaestroStackError(\n `upload-app requires app.source \"upload\" (current: \"${config.app.source}\").`,\n );\n }\n\n const filePath = path.resolve(cwd, config.app.path);\n logger.info(`Uploading app: ${config.app.path} ...`);\n\n const result = await uploadApp({\n auth: config.auth,\n filePath,\n customId: config.app.customId,\n });\n\n logger.info(\"Uploaded app:\");\n logger.info(`app_url: ${result.app_url}`);\n if (result.custom_id) logger.info(`custom_id: ${result.custom_id}`);\n if (result.expiry) logger.info(`expires: ${result.expiry}`);\n}\n","import { loadConfig } from \"../config/loadConfig.js\";\nimport { uploadSuite } from \"../browserstack/uploadSuite.js\";\nimport { createZip } from \"../suite/createZip.js\";\nimport { logger } from \"../utils/logger.js\";\nimport { validateAll, type GlobalOptions } from \"./validate.js\";\n\n/** Package and upload only the Maestro suite, printing the test_suite_url. */\nexport async function uploadSuiteCommand(opts: GlobalOptions): Promise<void> {\n const cwd = process.cwd();\n const loaded = await loadConfig(opts.config, cwd);\n const { config } = loaded;\n const { flows } = await validateAll(loaded, cwd);\n\n const { zipPath } = await createZip({\n files: flows,\n suiteRoot: config.suite.root,\n packageName: config.suite.packageName,\n cwd,\n });\n\n logger.info(`Uploading test suite (${flows.length} flow(s)) ...`);\n\n const result = await uploadSuite({\n auth: config.auth,\n zipPath,\n customId: config.suite.customId,\n });\n\n logger.info(\"Uploaded test suite:\");\n logger.info(`test_suite_url: ${result.test_suite_url}`);\n if (result.custom_id) logger.info(`custom_id: ${result.custom_id}`);\n if (result.expiry) logger.info(`expires: ${result.expiry}`);\n}\n"],"mappings":"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","names":["path","path","path","path","path","path","path","path","path","path","path","path","path","path","path","path","path","path","openAsBlob","path","FormData","FormData","openAsBlob","path","path","path","path"]}
|
package/package.json
CHANGED
|
@@ -1,17 +1,32 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "maestrostack",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Config-driven CLI for running Maestro mobile tests on BrowserStack App Automate.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"maestro",
|
|
7
7
|
"browserstack",
|
|
8
8
|
"app-automate",
|
|
9
|
+
"maestro browserstack",
|
|
9
10
|
"mobile-testing",
|
|
11
|
+
"mobile test automation",
|
|
12
|
+
"e2e testing",
|
|
10
13
|
"cli",
|
|
11
14
|
"android",
|
|
12
|
-
"ios"
|
|
15
|
+
"ios",
|
|
16
|
+
"appium-alternative",
|
|
17
|
+
"ci",
|
|
18
|
+
"typescript"
|
|
13
19
|
],
|
|
14
20
|
"license": "MIT",
|
|
21
|
+
"homepage": "https://rohanimmanuel.github.io/maestrostack/",
|
|
22
|
+
"repository": {
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": "git+https://github.com/RohanImmanuel/maestrostack.git"
|
|
25
|
+
},
|
|
26
|
+
"bugs": {
|
|
27
|
+
"url": "https://github.com/RohanImmanuel/maestrostack/issues"
|
|
28
|
+
},
|
|
29
|
+
"author": "Rohan Immanuel",
|
|
15
30
|
"type": "module",
|
|
16
31
|
"bin": {
|
|
17
32
|
"maestrostack": "dist/cli.js"
|