ariadne-studio 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.mjs
CHANGED
|
@@ -4012,10 +4012,10 @@ var require_resolve_block_map = __commonJS({
|
|
|
4012
4012
|
let offset = bm.offset;
|
|
4013
4013
|
let commentEnd = null;
|
|
4014
4014
|
for (const collItem of bm.items) {
|
|
4015
|
-
const { start, key, sep:
|
|
4015
|
+
const { start, key, sep: sep6, value } = collItem;
|
|
4016
4016
|
const keyProps = resolveProps.resolveProps(start, {
|
|
4017
4017
|
indicator: "explicit-key-ind",
|
|
4018
|
-
next: key ??
|
|
4018
|
+
next: key ?? sep6?.[0],
|
|
4019
4019
|
offset,
|
|
4020
4020
|
onError,
|
|
4021
4021
|
parentIndent: bm.indent,
|
|
@@ -4029,7 +4029,7 @@ var require_resolve_block_map = __commonJS({
|
|
|
4029
4029
|
else if ("indent" in key && key.indent !== bm.indent)
|
|
4030
4030
|
onError(offset, "BAD_INDENT", startColMsg);
|
|
4031
4031
|
}
|
|
4032
|
-
if (!keyProps.anchor && !keyProps.tag && !
|
|
4032
|
+
if (!keyProps.anchor && !keyProps.tag && !sep6) {
|
|
4033
4033
|
commentEnd = keyProps.end;
|
|
4034
4034
|
if (keyProps.comment) {
|
|
4035
4035
|
if (map.comment)
|
|
@@ -4053,7 +4053,7 @@ var require_resolve_block_map = __commonJS({
|
|
|
4053
4053
|
ctx.atKey = false;
|
|
4054
4054
|
if (utilMapIncludes.mapIncludes(ctx, map.items, keyNode))
|
|
4055
4055
|
onError(keyStart, "DUPLICATE_KEY", "Map keys must be unique");
|
|
4056
|
-
const valueProps = resolveProps.resolveProps(
|
|
4056
|
+
const valueProps = resolveProps.resolveProps(sep6 ?? [], {
|
|
4057
4057
|
indicator: "map-value-ind",
|
|
4058
4058
|
next: value,
|
|
4059
4059
|
offset: keyNode.range[2],
|
|
@@ -4069,7 +4069,7 @@ var require_resolve_block_map = __commonJS({
|
|
|
4069
4069
|
if (ctx.options.strict && keyProps.start < valueProps.found.offset - 1024)
|
|
4070
4070
|
onError(keyNode.range, "KEY_OVER_1024_CHARS", "The : indicator must be at most 1024 chars after the start of an implicit block mapping key");
|
|
4071
4071
|
}
|
|
4072
|
-
const valueNode = value ? composeNode(ctx, value, valueProps, onError) : composeEmptyNode(ctx, offset,
|
|
4072
|
+
const valueNode = value ? composeNode(ctx, value, valueProps, onError) : composeEmptyNode(ctx, offset, sep6, null, valueProps, onError);
|
|
4073
4073
|
if (ctx.schema.compat)
|
|
4074
4074
|
utilFlowIndentCheck.flowIndentCheck(bm.indent, value, onError);
|
|
4075
4075
|
offset = valueNode.range[2];
|
|
@@ -4160,7 +4160,7 @@ var require_resolve_end = __commonJS({
|
|
|
4160
4160
|
let comment = "";
|
|
4161
4161
|
if (end) {
|
|
4162
4162
|
let hasSpace = false;
|
|
4163
|
-
let
|
|
4163
|
+
let sep6 = "";
|
|
4164
4164
|
for (const token of end) {
|
|
4165
4165
|
const { source, type } = token;
|
|
4166
4166
|
switch (type) {
|
|
@@ -4174,13 +4174,13 @@ var require_resolve_end = __commonJS({
|
|
|
4174
4174
|
if (!comment)
|
|
4175
4175
|
comment = cb;
|
|
4176
4176
|
else
|
|
4177
|
-
comment +=
|
|
4178
|
-
|
|
4177
|
+
comment += sep6 + cb;
|
|
4178
|
+
sep6 = "";
|
|
4179
4179
|
break;
|
|
4180
4180
|
}
|
|
4181
4181
|
case "newline":
|
|
4182
4182
|
if (comment)
|
|
4183
|
-
|
|
4183
|
+
sep6 += source;
|
|
4184
4184
|
hasSpace = true;
|
|
4185
4185
|
break;
|
|
4186
4186
|
default:
|
|
@@ -4223,18 +4223,18 @@ var require_resolve_flow_collection = __commonJS({
|
|
|
4223
4223
|
let offset = fc.offset + fc.start.source.length;
|
|
4224
4224
|
for (let i = 0; i < fc.items.length; ++i) {
|
|
4225
4225
|
const collItem = fc.items[i];
|
|
4226
|
-
const { start, key, sep:
|
|
4226
|
+
const { start, key, sep: sep6, value } = collItem;
|
|
4227
4227
|
const props = resolveProps.resolveProps(start, {
|
|
4228
4228
|
flow: fcName,
|
|
4229
4229
|
indicator: "explicit-key-ind",
|
|
4230
|
-
next: key ??
|
|
4230
|
+
next: key ?? sep6?.[0],
|
|
4231
4231
|
offset,
|
|
4232
4232
|
onError,
|
|
4233
4233
|
parentIndent: fc.indent,
|
|
4234
4234
|
startOnNewline: false
|
|
4235
4235
|
});
|
|
4236
4236
|
if (!props.found) {
|
|
4237
|
-
if (!props.anchor && !props.tag && !
|
|
4237
|
+
if (!props.anchor && !props.tag && !sep6 && !value) {
|
|
4238
4238
|
if (i === 0 && props.comma)
|
|
4239
4239
|
onError(props.comma, "UNEXPECTED_TOKEN", `Unexpected , in ${fcName}`);
|
|
4240
4240
|
else if (i < fc.items.length - 1)
|
|
@@ -4288,8 +4288,8 @@ var require_resolve_flow_collection = __commonJS({
|
|
|
4288
4288
|
}
|
|
4289
4289
|
}
|
|
4290
4290
|
}
|
|
4291
|
-
if (!isMap && !
|
|
4292
|
-
const valueNode = value ? composeNode(ctx, value, props, onError) : composeEmptyNode(ctx, props.end,
|
|
4291
|
+
if (!isMap && !sep6 && !props.found) {
|
|
4292
|
+
const valueNode = value ? composeNode(ctx, value, props, onError) : composeEmptyNode(ctx, props.end, sep6, null, props, onError);
|
|
4293
4293
|
coll.items.push(valueNode);
|
|
4294
4294
|
offset = valueNode.range[2];
|
|
4295
4295
|
if (isBlock(value))
|
|
@@ -4301,7 +4301,7 @@ var require_resolve_flow_collection = __commonJS({
|
|
|
4301
4301
|
if (isBlock(key))
|
|
4302
4302
|
onError(keyNode.range, "BLOCK_IN_FLOW", blockMsg);
|
|
4303
4303
|
ctx.atKey = false;
|
|
4304
|
-
const valueProps = resolveProps.resolveProps(
|
|
4304
|
+
const valueProps = resolveProps.resolveProps(sep6 ?? [], {
|
|
4305
4305
|
flow: fcName,
|
|
4306
4306
|
indicator: "map-value-ind",
|
|
4307
4307
|
next: value,
|
|
@@ -4312,8 +4312,8 @@ var require_resolve_flow_collection = __commonJS({
|
|
|
4312
4312
|
});
|
|
4313
4313
|
if (valueProps.found) {
|
|
4314
4314
|
if (!isMap && !props.found && ctx.options.strict) {
|
|
4315
|
-
if (
|
|
4316
|
-
for (const st of
|
|
4315
|
+
if (sep6)
|
|
4316
|
+
for (const st of sep6) {
|
|
4317
4317
|
if (st === valueProps.found)
|
|
4318
4318
|
break;
|
|
4319
4319
|
if (st.type === "newline") {
|
|
@@ -4330,7 +4330,7 @@ var require_resolve_flow_collection = __commonJS({
|
|
|
4330
4330
|
else
|
|
4331
4331
|
onError(valueProps.start, "MISSING_CHAR", `Missing , or : between ${fcName} items`);
|
|
4332
4332
|
}
|
|
4333
|
-
const valueNode = value ? composeNode(ctx, value, valueProps, onError) : valueProps.found ? composeEmptyNode(ctx, valueProps.end,
|
|
4333
|
+
const valueNode = value ? composeNode(ctx, value, valueProps, onError) : valueProps.found ? composeEmptyNode(ctx, valueProps.end, sep6, null, valueProps, onError) : null;
|
|
4334
4334
|
if (valueNode) {
|
|
4335
4335
|
if (isBlock(value))
|
|
4336
4336
|
onError(valueNode.range, "BLOCK_IN_FLOW", blockMsg);
|
|
@@ -4510,7 +4510,7 @@ var require_resolve_block_scalar = __commonJS({
|
|
|
4510
4510
|
chompStart = i + 1;
|
|
4511
4511
|
}
|
|
4512
4512
|
let value = "";
|
|
4513
|
-
let
|
|
4513
|
+
let sep6 = "";
|
|
4514
4514
|
let prevMoreIndented = false;
|
|
4515
4515
|
for (let i = 0; i < contentStart; ++i)
|
|
4516
4516
|
value += lines[i][0].slice(trimIndent) + "\n";
|
|
@@ -4527,24 +4527,24 @@ var require_resolve_block_scalar = __commonJS({
|
|
|
4527
4527
|
indent = "";
|
|
4528
4528
|
}
|
|
4529
4529
|
if (type === Scalar2.Scalar.BLOCK_LITERAL) {
|
|
4530
|
-
value +=
|
|
4531
|
-
|
|
4530
|
+
value += sep6 + indent.slice(trimIndent) + content;
|
|
4531
|
+
sep6 = "\n";
|
|
4532
4532
|
} else if (indent.length > trimIndent || content[0] === " ") {
|
|
4533
|
-
if (
|
|
4534
|
-
|
|
4535
|
-
else if (!prevMoreIndented &&
|
|
4536
|
-
|
|
4537
|
-
value +=
|
|
4538
|
-
|
|
4533
|
+
if (sep6 === " ")
|
|
4534
|
+
sep6 = "\n";
|
|
4535
|
+
else if (!prevMoreIndented && sep6 === "\n")
|
|
4536
|
+
sep6 = "\n\n";
|
|
4537
|
+
value += sep6 + indent.slice(trimIndent) + content;
|
|
4538
|
+
sep6 = "\n";
|
|
4539
4539
|
prevMoreIndented = true;
|
|
4540
4540
|
} else if (content === "") {
|
|
4541
|
-
if (
|
|
4541
|
+
if (sep6 === "\n")
|
|
4542
4542
|
value += "\n";
|
|
4543
4543
|
else
|
|
4544
|
-
|
|
4544
|
+
sep6 = "\n";
|
|
4545
4545
|
} else {
|
|
4546
|
-
value +=
|
|
4547
|
-
|
|
4546
|
+
value += sep6 + content;
|
|
4547
|
+
sep6 = " ";
|
|
4548
4548
|
prevMoreIndented = false;
|
|
4549
4549
|
}
|
|
4550
4550
|
}
|
|
@@ -4727,25 +4727,25 @@ var require_resolve_flow_scalar = __commonJS({
|
|
|
4727
4727
|
trimBoth = /^[ \t]+|[ \t]+$/g;
|
|
4728
4728
|
}
|
|
4729
4729
|
let res = match[1].replace(trimEnd, "");
|
|
4730
|
-
let
|
|
4730
|
+
let sep6 = " ";
|
|
4731
4731
|
let pos = line.lastIndex;
|
|
4732
4732
|
while (match = line.exec(source)) {
|
|
4733
4733
|
const lm = match[1].replace(trimBoth, "");
|
|
4734
4734
|
if (lm === "") {
|
|
4735
|
-
if (
|
|
4736
|
-
res +=
|
|
4735
|
+
if (sep6 === "\n")
|
|
4736
|
+
res += sep6;
|
|
4737
4737
|
else
|
|
4738
|
-
|
|
4738
|
+
sep6 = "\n";
|
|
4739
4739
|
} else {
|
|
4740
|
-
res +=
|
|
4741
|
-
|
|
4740
|
+
res += sep6 + lm;
|
|
4741
|
+
sep6 = " ";
|
|
4742
4742
|
}
|
|
4743
4743
|
pos = line.lastIndex;
|
|
4744
4744
|
}
|
|
4745
4745
|
const last = /[ \t]*(.*)/sy;
|
|
4746
4746
|
last.lastIndex = pos;
|
|
4747
4747
|
match = last.exec(source);
|
|
4748
|
-
return res +
|
|
4748
|
+
return res + sep6 + (match?.[1] ?? "");
|
|
4749
4749
|
}
|
|
4750
4750
|
function doubleQuotedValue(source, onError) {
|
|
4751
4751
|
let res = "";
|
|
@@ -5555,14 +5555,14 @@ var require_cst_stringify = __commonJS({
|
|
|
5555
5555
|
}
|
|
5556
5556
|
}
|
|
5557
5557
|
}
|
|
5558
|
-
function stringifyItem({ start, key, sep:
|
|
5558
|
+
function stringifyItem({ start, key, sep: sep6, value }) {
|
|
5559
5559
|
let res = "";
|
|
5560
5560
|
for (const st of start)
|
|
5561
5561
|
res += st.source;
|
|
5562
5562
|
if (key)
|
|
5563
5563
|
res += stringifyToken(key);
|
|
5564
|
-
if (
|
|
5565
|
-
for (const st of
|
|
5564
|
+
if (sep6)
|
|
5565
|
+
for (const st of sep6)
|
|
5566
5566
|
res += st.source;
|
|
5567
5567
|
if (value)
|
|
5568
5568
|
res += stringifyToken(value);
|
|
@@ -6729,18 +6729,18 @@ var require_parser = __commonJS({
|
|
|
6729
6729
|
if (this.type === "map-value-ind") {
|
|
6730
6730
|
const prev = getPrevProps(this.peek(2));
|
|
6731
6731
|
const start = getFirstKeyStartProps(prev);
|
|
6732
|
-
let
|
|
6732
|
+
let sep6;
|
|
6733
6733
|
if (scalar.end) {
|
|
6734
|
-
|
|
6735
|
-
|
|
6734
|
+
sep6 = scalar.end;
|
|
6735
|
+
sep6.push(this.sourceToken);
|
|
6736
6736
|
delete scalar.end;
|
|
6737
6737
|
} else
|
|
6738
|
-
|
|
6738
|
+
sep6 = [this.sourceToken];
|
|
6739
6739
|
const map = {
|
|
6740
6740
|
type: "block-map",
|
|
6741
6741
|
offset: scalar.offset,
|
|
6742
6742
|
indent: scalar.indent,
|
|
6743
|
-
items: [{ start, key: scalar, sep:
|
|
6743
|
+
items: [{ start, key: scalar, sep: sep6 }]
|
|
6744
6744
|
};
|
|
6745
6745
|
this.onKeyLine = true;
|
|
6746
6746
|
this.stack[this.stack.length - 1] = map;
|
|
@@ -6893,15 +6893,15 @@ var require_parser = __commonJS({
|
|
|
6893
6893
|
} else if (isFlowToken(it.key) && !includesToken(it.sep, "newline")) {
|
|
6894
6894
|
const start2 = getFirstKeyStartProps(it.start);
|
|
6895
6895
|
const key = it.key;
|
|
6896
|
-
const
|
|
6897
|
-
|
|
6896
|
+
const sep6 = it.sep;
|
|
6897
|
+
sep6.push(this.sourceToken);
|
|
6898
6898
|
delete it.key;
|
|
6899
6899
|
delete it.sep;
|
|
6900
6900
|
this.stack.push({
|
|
6901
6901
|
type: "block-map",
|
|
6902
6902
|
offset: this.offset,
|
|
6903
6903
|
indent: this.indent,
|
|
6904
|
-
items: [{ start: start2, key, sep:
|
|
6904
|
+
items: [{ start: start2, key, sep: sep6 }]
|
|
6905
6905
|
});
|
|
6906
6906
|
} else if (start.length > 0) {
|
|
6907
6907
|
it.sep = it.sep.concat(start, this.sourceToken);
|
|
@@ -7095,13 +7095,13 @@ var require_parser = __commonJS({
|
|
|
7095
7095
|
const prev = getPrevProps(parent);
|
|
7096
7096
|
const start = getFirstKeyStartProps(prev);
|
|
7097
7097
|
fixFlowSeqItems(fc);
|
|
7098
|
-
const
|
|
7099
|
-
|
|
7098
|
+
const sep6 = fc.end.splice(1, fc.end.length);
|
|
7099
|
+
sep6.push(this.sourceToken);
|
|
7100
7100
|
const map = {
|
|
7101
7101
|
type: "block-map",
|
|
7102
7102
|
offset: fc.offset,
|
|
7103
7103
|
indent: fc.indent,
|
|
7104
|
-
items: [{ start, key: fc, sep:
|
|
7104
|
+
items: [{ start, key: fc, sep: sep6 }]
|
|
7105
7105
|
};
|
|
7106
7106
|
this.onKeyLine = true;
|
|
7107
7107
|
this.stack[this.stack.length - 1] = map;
|
|
@@ -11337,7 +11337,7 @@ var require_parseTokenList = __commonJS({
|
|
|
11337
11337
|
// ../../node_modules/.pnpm/depd@2.0.0/node_modules/depd/index.js
|
|
11338
11338
|
var require_depd = __commonJS({
|
|
11339
11339
|
"../../node_modules/.pnpm/depd@2.0.0/node_modules/depd/index.js"(exports, module) {
|
|
11340
|
-
var
|
|
11340
|
+
var relative5 = __require("path").relative;
|
|
11341
11341
|
module.exports = depd;
|
|
11342
11342
|
var basePath = process.cwd();
|
|
11343
11343
|
function containsNamespace(str, namespace) {
|
|
@@ -11529,7 +11529,7 @@ var require_depd = __commonJS({
|
|
|
11529
11529
|
return formatted;
|
|
11530
11530
|
}
|
|
11531
11531
|
function formatLocation(callSite) {
|
|
11532
|
-
return
|
|
11532
|
+
return relative5(basePath, callSite[0]) + ":" + callSite[1] + ":" + callSite[2];
|
|
11533
11533
|
}
|
|
11534
11534
|
function getStack() {
|
|
11535
11535
|
var limit = Error.stackTraceLimit;
|
|
@@ -12062,11 +12062,11 @@ var require_send = __commonJS({
|
|
|
12062
12062
|
var { parseTokenList } = require_parseTokenList();
|
|
12063
12063
|
var { createHttpError } = require_createHttpError();
|
|
12064
12064
|
var extname2 = path.extname;
|
|
12065
|
-
var
|
|
12065
|
+
var join18 = path.join;
|
|
12066
12066
|
var normalize = path.normalize;
|
|
12067
|
-
var
|
|
12068
|
-
var
|
|
12069
|
-
var
|
|
12067
|
+
var resolve7 = path.resolve;
|
|
12068
|
+
var sep6 = path.sep;
|
|
12069
|
+
var Readable2 = stream.Readable;
|
|
12070
12070
|
var BYTES_RANGE_REGEXP = /^ *bytes=/;
|
|
12071
12071
|
var MAX_MAXAGE = 60 * 60 * 24 * 365 * 1e3;
|
|
12072
12072
|
var UP_PATH_REGEXP = /(?:^|[\\/])\.\.(?:[\\/]|$)/;
|
|
@@ -12118,7 +12118,7 @@ var require_send = __commonJS({
|
|
|
12118
12118
|
}
|
|
12119
12119
|
onFile = options.onFile;
|
|
12120
12120
|
}
|
|
12121
|
-
const root = options.root ?
|
|
12121
|
+
const root = options.root ? resolve7(options.root) : null;
|
|
12122
12122
|
const highWaterMark = Number.isSafeInteger(options.highWaterMark) && options.highWaterMark > 0 ? options.highWaterMark : null;
|
|
12123
12123
|
return {
|
|
12124
12124
|
acceptRanges,
|
|
@@ -12150,21 +12150,21 @@ var require_send = __commonJS({
|
|
|
12150
12150
|
let parts;
|
|
12151
12151
|
if (root !== null) {
|
|
12152
12152
|
if (path2) {
|
|
12153
|
-
path2 = normalize("." +
|
|
12153
|
+
path2 = normalize("." + sep6 + path2);
|
|
12154
12154
|
}
|
|
12155
12155
|
if (UP_PATH_REGEXP.test(path2)) {
|
|
12156
12156
|
debug('malicious path "%s"', path2);
|
|
12157
12157
|
return { statusCode: 403 };
|
|
12158
12158
|
}
|
|
12159
|
-
parts = path2.split(
|
|
12160
|
-
path2 = normalize(
|
|
12159
|
+
parts = path2.split(sep6);
|
|
12160
|
+
path2 = normalize(join18(root, path2));
|
|
12161
12161
|
} else {
|
|
12162
12162
|
if (UP_PATH_REGEXP.test(path2)) {
|
|
12163
12163
|
debug('malicious path "%s"', path2);
|
|
12164
12164
|
return { statusCode: 403 };
|
|
12165
12165
|
}
|
|
12166
|
-
parts = normalize(path2).split(
|
|
12167
|
-
path2 =
|
|
12166
|
+
parts = normalize(path2).split(sep6);
|
|
12167
|
+
path2 = resolve7(path2);
|
|
12168
12168
|
}
|
|
12169
12169
|
return { path: path2, parts };
|
|
12170
12170
|
}
|
|
@@ -12260,9 +12260,9 @@ var require_send = __commonJS({
|
|
|
12260
12260
|
);
|
|
12261
12261
|
}
|
|
12262
12262
|
function tryStat(path2) {
|
|
12263
|
-
return new Promise((
|
|
12263
|
+
return new Promise((resolve8) => {
|
|
12264
12264
|
fs.stat(path2, function onstat(error, stat3) {
|
|
12265
|
-
|
|
12265
|
+
resolve8({ error, stat: stat3 });
|
|
12266
12266
|
});
|
|
12267
12267
|
});
|
|
12268
12268
|
}
|
|
@@ -12281,7 +12281,7 @@ var require_send = __commonJS({
|
|
|
12281
12281
|
return {
|
|
12282
12282
|
statusCode,
|
|
12283
12283
|
headers,
|
|
12284
|
-
stream:
|
|
12284
|
+
stream: Readable2.from(doc[0]),
|
|
12285
12285
|
// metadata
|
|
12286
12286
|
type: "error",
|
|
12287
12287
|
metadata: { error: createHttpError(statusCode, err) }
|
|
@@ -12307,7 +12307,7 @@ var require_send = __commonJS({
|
|
|
12307
12307
|
return {
|
|
12308
12308
|
statusCode: 304,
|
|
12309
12309
|
headers,
|
|
12310
|
-
stream:
|
|
12310
|
+
stream: Readable2.from(""),
|
|
12311
12311
|
// metadata
|
|
12312
12312
|
type: "file",
|
|
12313
12313
|
metadata: { path: path2, stat: stat3 }
|
|
@@ -12398,7 +12398,7 @@ var require_send = __commonJS({
|
|
|
12398
12398
|
return {
|
|
12399
12399
|
statusCode,
|
|
12400
12400
|
headers,
|
|
12401
|
-
stream:
|
|
12401
|
+
stream: Readable2.from(""),
|
|
12402
12402
|
// metadata
|
|
12403
12403
|
type: "file",
|
|
12404
12404
|
metadata: { path: path2, stat: stat3 }
|
|
@@ -12433,7 +12433,7 @@ var require_send = __commonJS({
|
|
|
12433
12433
|
return {
|
|
12434
12434
|
statusCode: 301,
|
|
12435
12435
|
headers,
|
|
12436
|
-
stream:
|
|
12436
|
+
stream: Readable2.from(doc[0]),
|
|
12437
12437
|
// metadata
|
|
12438
12438
|
type: "directory",
|
|
12439
12439
|
metadata: { requestPath: options.path, path: path2 }
|
|
@@ -12443,7 +12443,7 @@ var require_send = __commonJS({
|
|
|
12443
12443
|
let err;
|
|
12444
12444
|
for (let i = 0; i < options.index.length; i++) {
|
|
12445
12445
|
const index = options.index[i];
|
|
12446
|
-
const p =
|
|
12446
|
+
const p = join18(path2, index);
|
|
12447
12447
|
const { error, stat: stat3 } = await tryStat(p);
|
|
12448
12448
|
if (error) {
|
|
12449
12449
|
err = error;
|
|
@@ -12459,7 +12459,7 @@ var require_send = __commonJS({
|
|
|
12459
12459
|
}
|
|
12460
12460
|
async function sendFile(request, path2, options) {
|
|
12461
12461
|
const { error, stat: stat3 } = await tryStat(path2);
|
|
12462
|
-
if (error && error.code === "ENOENT" && !extname2(path2) && path2[path2.length - 1] !==
|
|
12462
|
+
if (error && error.code === "ENOENT" && !extname2(path2) && path2[path2.length - 1] !== sep6) {
|
|
12463
12463
|
let err = error;
|
|
12464
12464
|
for (let i = 0; i < options.extensions.length; i++) {
|
|
12465
12465
|
const extension = options.extensions[i];
|
|
@@ -13243,41 +13243,41 @@ var require_queue = __commonJS({
|
|
|
13243
13243
|
queue.drained = drained;
|
|
13244
13244
|
return queue;
|
|
13245
13245
|
function push(value) {
|
|
13246
|
-
var p = new Promise(function(
|
|
13246
|
+
var p = new Promise(function(resolve7, reject) {
|
|
13247
13247
|
pushCb(value, function(err, result) {
|
|
13248
13248
|
if (err) {
|
|
13249
13249
|
reject(err);
|
|
13250
13250
|
return;
|
|
13251
13251
|
}
|
|
13252
|
-
|
|
13252
|
+
resolve7(result);
|
|
13253
13253
|
});
|
|
13254
13254
|
});
|
|
13255
13255
|
p.catch(noop);
|
|
13256
13256
|
return p;
|
|
13257
13257
|
}
|
|
13258
13258
|
function unshift(value) {
|
|
13259
|
-
var p = new Promise(function(
|
|
13259
|
+
var p = new Promise(function(resolve7, reject) {
|
|
13260
13260
|
unshiftCb(value, function(err, result) {
|
|
13261
13261
|
if (err) {
|
|
13262
13262
|
reject(err);
|
|
13263
13263
|
return;
|
|
13264
13264
|
}
|
|
13265
|
-
|
|
13265
|
+
resolve7(result);
|
|
13266
13266
|
});
|
|
13267
13267
|
});
|
|
13268
13268
|
p.catch(noop);
|
|
13269
13269
|
return p;
|
|
13270
13270
|
}
|
|
13271
13271
|
function drained() {
|
|
13272
|
-
var p = new Promise(function(
|
|
13272
|
+
var p = new Promise(function(resolve7) {
|
|
13273
13273
|
process.nextTick(function() {
|
|
13274
13274
|
if (queue.idle()) {
|
|
13275
|
-
|
|
13275
|
+
resolve7();
|
|
13276
13276
|
} else {
|
|
13277
13277
|
var previousDrain = queue.drain;
|
|
13278
13278
|
queue.drain = function() {
|
|
13279
13279
|
if (typeof previousDrain === "function") previousDrain();
|
|
13280
|
-
|
|
13280
|
+
resolve7();
|
|
13281
13281
|
queue.drain = previousDrain;
|
|
13282
13282
|
};
|
|
13283
13283
|
}
|
|
@@ -13617,7 +13617,7 @@ var require_static = __commonJS({
|
|
|
13617
13617
|
var path = __require("node:path");
|
|
13618
13618
|
var { fileURLToPath: fileURLToPath4 } = __require("node:url");
|
|
13619
13619
|
var { statSync: statSync2 } = __require("node:fs");
|
|
13620
|
-
var { readdir:
|
|
13620
|
+
var { readdir: readdir9, stat: stat3 } = __require("node:fs/promises");
|
|
13621
13621
|
var { basename: basename3 } = __require("node:path");
|
|
13622
13622
|
var { glob } = require_index_min();
|
|
13623
13623
|
var fp = require_fastify_plugin();
|
|
@@ -13796,8 +13796,8 @@ var require_static = __commonJS({
|
|
|
13796
13796
|
}
|
|
13797
13797
|
}
|
|
13798
13798
|
}
|
|
13799
|
-
for (const [
|
|
13800
|
-
const pathname =
|
|
13799
|
+
for (const [dirname11, rootPath] of indexDirs.entries()) {
|
|
13800
|
+
const pathname = dirname11 + (dirname11.endsWith("/") ? "" : "/");
|
|
13801
13801
|
const file = "/" + pathname.replace(prefix, "");
|
|
13802
13802
|
setUpHeadAndGet(routeOpts, pathname, file, rootPath);
|
|
13803
13803
|
if (opts.redirect === true) {
|
|
@@ -14153,7 +14153,7 @@ var require_static = __commonJS({
|
|
|
14153
14153
|
for (const segment of segments) {
|
|
14154
14154
|
let entries;
|
|
14155
14155
|
try {
|
|
14156
|
-
entries = await
|
|
14156
|
+
entries = await readdir9(parent);
|
|
14157
14157
|
} catch (error) {
|
|
14158
14158
|
return { status: isMissingPathError(error) ? "missing" : "unverifiable" };
|
|
14159
14159
|
}
|
|
@@ -14599,8 +14599,8 @@ var require_create_promise = __commonJS({
|
|
|
14599
14599
|
reject: null,
|
|
14600
14600
|
promise: null
|
|
14601
14601
|
};
|
|
14602
|
-
obj.promise = new Promise((
|
|
14603
|
-
obj.resolve =
|
|
14602
|
+
obj.promise = new Promise((resolve7, reject) => {
|
|
14603
|
+
obj.resolve = resolve7;
|
|
14604
14604
|
obj.reject = reject;
|
|
14605
14605
|
});
|
|
14606
14606
|
return obj;
|
|
@@ -14873,11 +14873,11 @@ var require_thenify = __commonJS({
|
|
|
14873
14873
|
return;
|
|
14874
14874
|
}
|
|
14875
14875
|
debug("thenify");
|
|
14876
|
-
return (
|
|
14876
|
+
return (resolve7, reject) => {
|
|
14877
14877
|
const p = this._loadRegistered();
|
|
14878
14878
|
return p.then(() => {
|
|
14879
14879
|
this[kThenifyDoNotWrap] = true;
|
|
14880
|
-
return
|
|
14880
|
+
return resolve7(this._server);
|
|
14881
14881
|
}, reject);
|
|
14882
14882
|
};
|
|
14883
14883
|
}
|
|
@@ -15015,12 +15015,12 @@ var require_avvio = __commonJS({
|
|
|
15015
15015
|
inherits(Boot, EE);
|
|
15016
15016
|
if ("asyncDispose" in Symbol) {
|
|
15017
15017
|
Boot.prototype[Symbol.asyncDispose] = function() {
|
|
15018
|
-
return new Promise((
|
|
15018
|
+
return new Promise((resolve7, reject) => {
|
|
15019
15019
|
this.close((err) => {
|
|
15020
15020
|
if (err) {
|
|
15021
15021
|
return reject(err);
|
|
15022
15022
|
}
|
|
15023
|
-
|
|
15023
|
+
resolve7();
|
|
15024
15024
|
});
|
|
15025
15025
|
});
|
|
15026
15026
|
};
|
|
@@ -15167,12 +15167,12 @@ var require_avvio = __commonJS({
|
|
|
15167
15167
|
throw new AVV_ERR_CALLBACK_NOT_FN("close", typeof func);
|
|
15168
15168
|
}
|
|
15169
15169
|
} else {
|
|
15170
|
-
promise = new Promise(function(
|
|
15170
|
+
promise = new Promise(function(resolve7, reject) {
|
|
15171
15171
|
func = function(err) {
|
|
15172
15172
|
if (err) {
|
|
15173
15173
|
return reject(err);
|
|
15174
15174
|
}
|
|
15175
|
-
|
|
15175
|
+
resolve7();
|
|
15176
15176
|
};
|
|
15177
15177
|
});
|
|
15178
15178
|
}
|
|
@@ -15192,7 +15192,7 @@ var require_avvio = __commonJS({
|
|
|
15192
15192
|
queueMicrotask(this.start.bind(this));
|
|
15193
15193
|
return;
|
|
15194
15194
|
}
|
|
15195
|
-
return new Promise((
|
|
15195
|
+
return new Promise((resolve7, reject) => {
|
|
15196
15196
|
this._readyQ.push(readyPromiseCB);
|
|
15197
15197
|
this.start();
|
|
15198
15198
|
const relativeContext = this._current[0].server;
|
|
@@ -15200,7 +15200,7 @@ var require_avvio = __commonJS({
|
|
|
15200
15200
|
if (err) {
|
|
15201
15201
|
reject(err);
|
|
15202
15202
|
} else {
|
|
15203
|
-
|
|
15203
|
+
resolve7(relativeContext);
|
|
15204
15204
|
}
|
|
15205
15205
|
process.nextTick(done);
|
|
15206
15206
|
}
|
|
@@ -16595,8 +16595,8 @@ var require_promise = __commonJS({
|
|
|
16595
16595
|
var { kTestInternals } = require_symbols2();
|
|
16596
16596
|
function withResolvers() {
|
|
16597
16597
|
let res, rej;
|
|
16598
|
-
const promise = new Promise((
|
|
16599
|
-
res =
|
|
16598
|
+
const promise = new Promise((resolve7, reject) => {
|
|
16599
|
+
res = resolve7;
|
|
16600
16600
|
rej = reject;
|
|
16601
16601
|
});
|
|
16602
16602
|
return { promise, resolve: res, reject: rej };
|
|
@@ -16695,15 +16695,15 @@ var require_server = __commonJS({
|
|
|
16695
16695
|
if (cb === void 0) {
|
|
16696
16696
|
const listening = listenPromise.call(this, server, listenOptions);
|
|
16697
16697
|
return listening.then((address) => {
|
|
16698
|
-
const { promise, resolve:
|
|
16698
|
+
const { promise, resolve: resolve7 } = PonyPromise.withResolvers();
|
|
16699
16699
|
if (host === "localhost") {
|
|
16700
16700
|
multipleBindings.call(this, server, httpHandler, options, listenOptions, () => {
|
|
16701
16701
|
this[kState].listening = true;
|
|
16702
|
-
|
|
16702
|
+
resolve7(address);
|
|
16703
16703
|
onListenHookRunner(this);
|
|
16704
16704
|
});
|
|
16705
16705
|
} else {
|
|
16706
|
-
|
|
16706
|
+
resolve7(address);
|
|
16707
16707
|
onListenHookRunner(this);
|
|
16708
16708
|
}
|
|
16709
16709
|
return promise;
|
|
@@ -16830,7 +16830,7 @@ var require_server = __commonJS({
|
|
|
16830
16830
|
}
|
|
16831
16831
|
return this.ready().then(() => {
|
|
16832
16832
|
if (this[kState].aborted) return;
|
|
16833
|
-
const { promise, resolve:
|
|
16833
|
+
const { promise, resolve: resolve7, reject } = PonyPromise.withResolvers();
|
|
16834
16834
|
const errEventHandler = (err) => {
|
|
16835
16835
|
cleanup();
|
|
16836
16836
|
this[kState].listening = false;
|
|
@@ -16839,7 +16839,7 @@ var require_server = __commonJS({
|
|
|
16839
16839
|
const listeningEventHandler = () => {
|
|
16840
16840
|
cleanup();
|
|
16841
16841
|
this[kState].listening = true;
|
|
16842
|
-
|
|
16842
|
+
resolve7(logServerAddress.call(
|
|
16843
16843
|
this,
|
|
16844
16844
|
server,
|
|
16845
16845
|
listenOptions.listenTextResolver || defaultResolveServerListeningText
|
|
@@ -21116,7 +21116,7 @@ var require_atomic_sleep = __commonJS({
|
|
|
21116
21116
|
"../../node_modules/.pnpm/atomic-sleep@1.0.0/node_modules/atomic-sleep/index.js"(exports, module) {
|
|
21117
21117
|
"use strict";
|
|
21118
21118
|
if (typeof SharedArrayBuffer !== "undefined" && typeof Atomics !== "undefined") {
|
|
21119
|
-
let
|
|
21119
|
+
let sleep2 = function(ms) {
|
|
21120
21120
|
const valid = ms > 0 && ms < Infinity;
|
|
21121
21121
|
if (valid === false) {
|
|
21122
21122
|
if (typeof ms !== "number" && typeof ms !== "bigint") {
|
|
@@ -21127,9 +21127,9 @@ var require_atomic_sleep = __commonJS({
|
|
|
21127
21127
|
Atomics.wait(nil, 0, 0, Number(ms));
|
|
21128
21128
|
};
|
|
21129
21129
|
const nil = new Int32Array(new SharedArrayBuffer(4));
|
|
21130
|
-
module.exports =
|
|
21130
|
+
module.exports = sleep2;
|
|
21131
21131
|
} else {
|
|
21132
|
-
let
|
|
21132
|
+
let sleep2 = function(ms) {
|
|
21133
21133
|
const valid = ms > 0 && ms < Infinity;
|
|
21134
21134
|
if (valid === false) {
|
|
21135
21135
|
if (typeof ms !== "number" && typeof ms !== "bigint") {
|
|
@@ -21141,7 +21141,7 @@ var require_atomic_sleep = __commonJS({
|
|
|
21141
21141
|
while (target > Date.now()) {
|
|
21142
21142
|
}
|
|
21143
21143
|
};
|
|
21144
|
-
module.exports =
|
|
21144
|
+
module.exports = sleep2;
|
|
21145
21145
|
}
|
|
21146
21146
|
}
|
|
21147
21147
|
});
|
|
@@ -21154,7 +21154,7 @@ var require_sonic_boom = __commonJS({
|
|
|
21154
21154
|
var EventEmitter = __require("events");
|
|
21155
21155
|
var inherits = __require("util").inherits;
|
|
21156
21156
|
var path = __require("path");
|
|
21157
|
-
var
|
|
21157
|
+
var sleep2 = require_atomic_sleep();
|
|
21158
21158
|
var assert = __require("assert");
|
|
21159
21159
|
var BUSY_WRITE_TIMEOUT = 100;
|
|
21160
21160
|
var kEmptyBuffer = Buffer.allocUnsafe(0);
|
|
@@ -21227,7 +21227,7 @@ var require_sonic_boom = __commonJS({
|
|
|
21227
21227
|
if (!(this instanceof SonicBoom)) {
|
|
21228
21228
|
return new SonicBoom(opts);
|
|
21229
21229
|
}
|
|
21230
|
-
let { fd, dest, minLength, maxLength, maxWrite, periodicFlush, sync, append = true, mkdir:
|
|
21230
|
+
let { fd, dest, minLength, maxLength, maxWrite, periodicFlush, sync, append = true, mkdir: mkdir13, retryEAGAIN, fsync, contentMode, mode } = opts || {};
|
|
21231
21231
|
fd = fd || dest;
|
|
21232
21232
|
this._len = 0;
|
|
21233
21233
|
this.fd = -1;
|
|
@@ -21252,7 +21252,7 @@ var require_sonic_boom = __commonJS({
|
|
|
21252
21252
|
this.append = append || false;
|
|
21253
21253
|
this.mode = mode;
|
|
21254
21254
|
this.retryEAGAIN = retryEAGAIN || (() => true);
|
|
21255
|
-
this.mkdir =
|
|
21255
|
+
this.mkdir = mkdir13 || false;
|
|
21256
21256
|
let fsWriteSync;
|
|
21257
21257
|
let fsWrite;
|
|
21258
21258
|
if (contentMode === kContentModeBuffer) {
|
|
@@ -21300,7 +21300,7 @@ var require_sonic_boom = __commonJS({
|
|
|
21300
21300
|
if ((err.code === "EAGAIN" || err.code === "EBUSY") && this.retryEAGAIN(err, this._writingBuf.length, this._len - this._writingBuf.length)) {
|
|
21301
21301
|
if (this.sync) {
|
|
21302
21302
|
try {
|
|
21303
|
-
|
|
21303
|
+
sleep2(BUSY_WRITE_TIMEOUT);
|
|
21304
21304
|
this.release(void 0, 0);
|
|
21305
21305
|
} catch (err2) {
|
|
21306
21306
|
this.release(err2);
|
|
@@ -21613,7 +21613,7 @@ var require_sonic_boom = __commonJS({
|
|
|
21613
21613
|
if (shouldRetry && !this.retryEAGAIN(err, buf.length, this._len - buf.length)) {
|
|
21614
21614
|
throw err;
|
|
21615
21615
|
}
|
|
21616
|
-
|
|
21616
|
+
sleep2(BUSY_WRITE_TIMEOUT);
|
|
21617
21617
|
}
|
|
21618
21618
|
}
|
|
21619
21619
|
try {
|
|
@@ -21650,7 +21650,7 @@ var require_sonic_boom = __commonJS({
|
|
|
21650
21650
|
if (shouldRetry && !this.retryEAGAIN(err, buf.length, this._len - buf.length)) {
|
|
21651
21651
|
throw err;
|
|
21652
21652
|
}
|
|
21653
|
-
|
|
21653
|
+
sleep2(BUSY_WRITE_TIMEOUT);
|
|
21654
21654
|
}
|
|
21655
21655
|
}
|
|
21656
21656
|
}
|
|
@@ -21964,7 +21964,7 @@ var require_thread_stream = __commonJS({
|
|
|
21964
21964
|
var { version: version2 } = require_package();
|
|
21965
21965
|
var { EventEmitter } = __require("events");
|
|
21966
21966
|
var { Worker } = __require("worker_threads");
|
|
21967
|
-
var { join:
|
|
21967
|
+
var { join: join18 } = __require("path");
|
|
21968
21968
|
var { pathToFileURL } = __require("url");
|
|
21969
21969
|
var { wait } = require_wait();
|
|
21970
21970
|
var {
|
|
@@ -22015,7 +22015,7 @@ var require_thread_stream = __commonJS({
|
|
|
22015
22015
|
function createWorker(stream, opts) {
|
|
22016
22016
|
const { filename, workerData } = opts;
|
|
22017
22017
|
const bundlerOverrides = "__bundlerPathsOverrides" in globalThis ? globalThis.__bundlerPathsOverrides : {};
|
|
22018
|
-
const toExecute = bundlerOverrides["thread-stream-worker"] ||
|
|
22018
|
+
const toExecute = bundlerOverrides["thread-stream-worker"] || join18(__dirname, "lib", "worker.js");
|
|
22019
22019
|
const worker = new Worker(toExecute, {
|
|
22020
22020
|
...opts.workerOpts,
|
|
22021
22021
|
name: opts.workerOpts?.name || "thread-stream",
|
|
@@ -22481,11 +22481,11 @@ var require_transport = __commonJS({
|
|
|
22481
22481
|
"../../node_modules/.pnpm/pino@10.4.0/node_modules/pino/lib/transport.js"(exports, module) {
|
|
22482
22482
|
"use strict";
|
|
22483
22483
|
var { createRequire: createRequire2 } = __require("module");
|
|
22484
|
-
var { existsSync:
|
|
22484
|
+
var { existsSync: existsSync10 } = __require("node:fs");
|
|
22485
22485
|
var getCallers = require_caller();
|
|
22486
|
-
var { join:
|
|
22486
|
+
var { join: join18, isAbsolute: isAbsolute2, sep: sep6 } = __require("node:path");
|
|
22487
22487
|
var { fileURLToPath: fileURLToPath4 } = __require("node:url");
|
|
22488
|
-
var
|
|
22488
|
+
var sleep2 = require_atomic_sleep();
|
|
22489
22489
|
var onExit = require_on_exit_leak_free();
|
|
22490
22490
|
var ThreadStream = require_thread_stream();
|
|
22491
22491
|
var { transportUsesMultistreamSym } = require_symbols3();
|
|
@@ -22556,7 +22556,7 @@ var require_transport = __commonJS({
|
|
|
22556
22556
|
return false;
|
|
22557
22557
|
}
|
|
22558
22558
|
}
|
|
22559
|
-
return isAbsolute2(path) && !
|
|
22559
|
+
return isAbsolute2(path) && !existsSync10(path);
|
|
22560
22560
|
}
|
|
22561
22561
|
function stripQuotes(value) {
|
|
22562
22562
|
const first = value[0];
|
|
@@ -22609,7 +22609,7 @@ var require_transport = __commonJS({
|
|
|
22609
22609
|
return;
|
|
22610
22610
|
}
|
|
22611
22611
|
stream.flushSync();
|
|
22612
|
-
|
|
22612
|
+
sleep2(100);
|
|
22613
22613
|
stream.end();
|
|
22614
22614
|
}
|
|
22615
22615
|
return stream;
|
|
@@ -22626,7 +22626,7 @@ var require_transport = __commonJS({
|
|
|
22626
22626
|
stream.flushSync();
|
|
22627
22627
|
}
|
|
22628
22628
|
function transport(fullOptions) {
|
|
22629
|
-
const { pipeline, targets, levels, dedupe, worker = {}, caller = getCallers(), sync = false } = fullOptions;
|
|
22629
|
+
const { pipeline: pipeline2, targets, levels, dedupe, worker = {}, caller = getCallers(), sync = false } = fullOptions;
|
|
22630
22630
|
const options = {
|
|
22631
22631
|
...fullOptions.options
|
|
22632
22632
|
};
|
|
@@ -22638,7 +22638,7 @@ var require_transport = __commonJS({
|
|
|
22638
22638
|
throw new Error("only one of target or targets can be specified");
|
|
22639
22639
|
}
|
|
22640
22640
|
if (targets) {
|
|
22641
|
-
target = bundlerOverrides["pino-worker"] ||
|
|
22641
|
+
target = bundlerOverrides["pino-worker"] || join18(__dirname, "worker.js");
|
|
22642
22642
|
options.targets = targets.filter((dest) => dest.target).map((dest) => {
|
|
22643
22643
|
return {
|
|
22644
22644
|
...dest,
|
|
@@ -22658,9 +22658,9 @@ var require_transport = __commonJS({
|
|
|
22658
22658
|
const targetsHaveLevel = options.targets.some((t) => t.level != null);
|
|
22659
22659
|
const pipelinesHaveLevel = options.pipelines.some((p) => p.some((t) => t.level != null));
|
|
22660
22660
|
usesMultistream = options.targets.length + options.pipelines.length > 1 || targetsHaveLevel || pipelinesHaveLevel;
|
|
22661
|
-
} else if (
|
|
22662
|
-
target = bundlerOverrides["pino-worker"] ||
|
|
22663
|
-
options.pipelines = [
|
|
22661
|
+
} else if (pipeline2) {
|
|
22662
|
+
target = bundlerOverrides["pino-worker"] || join18(__dirname, "worker.js");
|
|
22663
|
+
options.pipelines = [pipeline2.map((dest) => {
|
|
22664
22664
|
return {
|
|
22665
22665
|
...dest,
|
|
22666
22666
|
target: fixTarget(dest.target)
|
|
@@ -22674,7 +22674,7 @@ var require_transport = __commonJS({
|
|
|
22674
22674
|
options.dedupe = dedupe;
|
|
22675
22675
|
}
|
|
22676
22676
|
options.pinoWillSendConfig = true;
|
|
22677
|
-
const name = targets ||
|
|
22677
|
+
const name = targets || pipeline2 ? "pino.transport" : target;
|
|
22678
22678
|
const stream = buildStream(fixTarget(target), options, worker, sync, name);
|
|
22679
22679
|
if (usesMultistream) {
|
|
22680
22680
|
stream[transportUsesMultistreamSym] = true;
|
|
@@ -22686,12 +22686,12 @@ var require_transport = __commonJS({
|
|
|
22686
22686
|
return origin;
|
|
22687
22687
|
}
|
|
22688
22688
|
if (origin === "pino/file") {
|
|
22689
|
-
return
|
|
22689
|
+
return join18(__dirname, "..", "file.js");
|
|
22690
22690
|
}
|
|
22691
22691
|
let fixTarget2;
|
|
22692
22692
|
for (const filePath of callers) {
|
|
22693
22693
|
try {
|
|
22694
|
-
const context = filePath === "node:repl" ? process.cwd() +
|
|
22694
|
+
const context = filePath === "node:repl" ? process.cwd() + sep6 : filePath;
|
|
22695
22695
|
fixTarget2 = createRequire2(context).resolve(origin);
|
|
22696
22696
|
break;
|
|
22697
22697
|
} catch (err) {
|
|
@@ -23666,7 +23666,7 @@ var require_safe_stable_stringify = __commonJS({
|
|
|
23666
23666
|
return circularValue;
|
|
23667
23667
|
}
|
|
23668
23668
|
let res = "";
|
|
23669
|
-
let
|
|
23669
|
+
let join18 = ",";
|
|
23670
23670
|
const originalIndentation = indentation;
|
|
23671
23671
|
if (Array.isArray(value)) {
|
|
23672
23672
|
if (value.length === 0) {
|
|
@@ -23680,7 +23680,7 @@ var require_safe_stable_stringify = __commonJS({
|
|
|
23680
23680
|
indentation += spacer;
|
|
23681
23681
|
res += `
|
|
23682
23682
|
${indentation}`;
|
|
23683
|
-
|
|
23683
|
+
join18 = `,
|
|
23684
23684
|
${indentation}`;
|
|
23685
23685
|
}
|
|
23686
23686
|
const maximumValuesToStringify = Math.min(value.length, maximumBreadth);
|
|
@@ -23688,13 +23688,13 @@ ${indentation}`;
|
|
|
23688
23688
|
for (; i < maximumValuesToStringify - 1; i++) {
|
|
23689
23689
|
const tmp2 = stringifyFnReplacer(String(i), value, stack, replacer, spacer, indentation);
|
|
23690
23690
|
res += tmp2 !== void 0 ? tmp2 : "null";
|
|
23691
|
-
res +=
|
|
23691
|
+
res += join18;
|
|
23692
23692
|
}
|
|
23693
23693
|
const tmp = stringifyFnReplacer(String(i), value, stack, replacer, spacer, indentation);
|
|
23694
23694
|
res += tmp !== void 0 ? tmp : "null";
|
|
23695
23695
|
if (value.length - 1 > maximumBreadth) {
|
|
23696
23696
|
const removedKeys = value.length - maximumBreadth - 1;
|
|
23697
|
-
res += `${
|
|
23697
|
+
res += `${join18}"... ${getItemCount(removedKeys)} not stringified"`;
|
|
23698
23698
|
}
|
|
23699
23699
|
if (spacer !== "") {
|
|
23700
23700
|
res += `
|
|
@@ -23715,7 +23715,7 @@ ${originalIndentation}`;
|
|
|
23715
23715
|
let separator = "";
|
|
23716
23716
|
if (spacer !== "") {
|
|
23717
23717
|
indentation += spacer;
|
|
23718
|
-
|
|
23718
|
+
join18 = `,
|
|
23719
23719
|
${indentation}`;
|
|
23720
23720
|
whitespace = " ";
|
|
23721
23721
|
}
|
|
@@ -23729,13 +23729,13 @@ ${indentation}`;
|
|
|
23729
23729
|
const tmp = stringifyFnReplacer(key2, value, stack, replacer, spacer, indentation);
|
|
23730
23730
|
if (tmp !== void 0) {
|
|
23731
23731
|
res += `${separator}${strEscape(key2)}:${whitespace}${tmp}`;
|
|
23732
|
-
separator =
|
|
23732
|
+
separator = join18;
|
|
23733
23733
|
}
|
|
23734
23734
|
}
|
|
23735
23735
|
if (keyLength > maximumBreadth) {
|
|
23736
23736
|
const removedKeys = keyLength - maximumBreadth;
|
|
23737
23737
|
res += `${separator}"...":${whitespace}"${getItemCount(removedKeys)} not stringified"`;
|
|
23738
|
-
separator =
|
|
23738
|
+
separator = join18;
|
|
23739
23739
|
}
|
|
23740
23740
|
if (spacer !== "" && separator.length > 1) {
|
|
23741
23741
|
res = `
|
|
@@ -23776,7 +23776,7 @@ ${originalIndentation}`;
|
|
|
23776
23776
|
}
|
|
23777
23777
|
const originalIndentation = indentation;
|
|
23778
23778
|
let res = "";
|
|
23779
|
-
let
|
|
23779
|
+
let join18 = ",";
|
|
23780
23780
|
if (Array.isArray(value)) {
|
|
23781
23781
|
if (value.length === 0) {
|
|
23782
23782
|
return "[]";
|
|
@@ -23789,7 +23789,7 @@ ${originalIndentation}`;
|
|
|
23789
23789
|
indentation += spacer;
|
|
23790
23790
|
res += `
|
|
23791
23791
|
${indentation}`;
|
|
23792
|
-
|
|
23792
|
+
join18 = `,
|
|
23793
23793
|
${indentation}`;
|
|
23794
23794
|
}
|
|
23795
23795
|
const maximumValuesToStringify = Math.min(value.length, maximumBreadth);
|
|
@@ -23797,13 +23797,13 @@ ${indentation}`;
|
|
|
23797
23797
|
for (; i < maximumValuesToStringify - 1; i++) {
|
|
23798
23798
|
const tmp2 = stringifyArrayReplacer(String(i), value[i], stack, replacer, spacer, indentation);
|
|
23799
23799
|
res += tmp2 !== void 0 ? tmp2 : "null";
|
|
23800
|
-
res +=
|
|
23800
|
+
res += join18;
|
|
23801
23801
|
}
|
|
23802
23802
|
const tmp = stringifyArrayReplacer(String(i), value[i], stack, replacer, spacer, indentation);
|
|
23803
23803
|
res += tmp !== void 0 ? tmp : "null";
|
|
23804
23804
|
if (value.length - 1 > maximumBreadth) {
|
|
23805
23805
|
const removedKeys = value.length - maximumBreadth - 1;
|
|
23806
|
-
res += `${
|
|
23806
|
+
res += `${join18}"... ${getItemCount(removedKeys)} not stringified"`;
|
|
23807
23807
|
}
|
|
23808
23808
|
if (spacer !== "") {
|
|
23809
23809
|
res += `
|
|
@@ -23816,7 +23816,7 @@ ${originalIndentation}`;
|
|
|
23816
23816
|
let whitespace = "";
|
|
23817
23817
|
if (spacer !== "") {
|
|
23818
23818
|
indentation += spacer;
|
|
23819
|
-
|
|
23819
|
+
join18 = `,
|
|
23820
23820
|
${indentation}`;
|
|
23821
23821
|
whitespace = " ";
|
|
23822
23822
|
}
|
|
@@ -23825,7 +23825,7 @@ ${indentation}`;
|
|
|
23825
23825
|
const tmp = stringifyArrayReplacer(key2, value[key2], stack, replacer, spacer, indentation);
|
|
23826
23826
|
if (tmp !== void 0) {
|
|
23827
23827
|
res += `${separator}${strEscape(key2)}:${whitespace}${tmp}`;
|
|
23828
|
-
separator =
|
|
23828
|
+
separator = join18;
|
|
23829
23829
|
}
|
|
23830
23830
|
}
|
|
23831
23831
|
if (spacer !== "" && separator.length > 1) {
|
|
@@ -23883,20 +23883,20 @@ ${originalIndentation}`;
|
|
|
23883
23883
|
indentation += spacer;
|
|
23884
23884
|
let res2 = `
|
|
23885
23885
|
${indentation}`;
|
|
23886
|
-
const
|
|
23886
|
+
const join19 = `,
|
|
23887
23887
|
${indentation}`;
|
|
23888
23888
|
const maximumValuesToStringify = Math.min(value.length, maximumBreadth);
|
|
23889
23889
|
let i = 0;
|
|
23890
23890
|
for (; i < maximumValuesToStringify - 1; i++) {
|
|
23891
23891
|
const tmp2 = stringifyIndent(String(i), value[i], stack, spacer, indentation);
|
|
23892
23892
|
res2 += tmp2 !== void 0 ? tmp2 : "null";
|
|
23893
|
-
res2 +=
|
|
23893
|
+
res2 += join19;
|
|
23894
23894
|
}
|
|
23895
23895
|
const tmp = stringifyIndent(String(i), value[i], stack, spacer, indentation);
|
|
23896
23896
|
res2 += tmp !== void 0 ? tmp : "null";
|
|
23897
23897
|
if (value.length - 1 > maximumBreadth) {
|
|
23898
23898
|
const removedKeys = value.length - maximumBreadth - 1;
|
|
23899
|
-
res2 += `${
|
|
23899
|
+
res2 += `${join19}"... ${getItemCount(removedKeys)} not stringified"`;
|
|
23900
23900
|
}
|
|
23901
23901
|
res2 += `
|
|
23902
23902
|
${originalIndentation}`;
|
|
@@ -23912,16 +23912,16 @@ ${originalIndentation}`;
|
|
|
23912
23912
|
return '"[Object]"';
|
|
23913
23913
|
}
|
|
23914
23914
|
indentation += spacer;
|
|
23915
|
-
const
|
|
23915
|
+
const join18 = `,
|
|
23916
23916
|
${indentation}`;
|
|
23917
23917
|
let res = "";
|
|
23918
23918
|
let separator = "";
|
|
23919
23919
|
let maximumPropertiesToStringify = Math.min(keyLength, maximumBreadth);
|
|
23920
23920
|
if (isTypedArrayWithEntries(value)) {
|
|
23921
|
-
res += stringifyTypedArray(value,
|
|
23921
|
+
res += stringifyTypedArray(value, join18, maximumBreadth);
|
|
23922
23922
|
keys = keys.slice(value.length);
|
|
23923
23923
|
maximumPropertiesToStringify -= value.length;
|
|
23924
|
-
separator =
|
|
23924
|
+
separator = join18;
|
|
23925
23925
|
}
|
|
23926
23926
|
if (deterministic) {
|
|
23927
23927
|
keys = sort(keys, comparator);
|
|
@@ -23932,13 +23932,13 @@ ${indentation}`;
|
|
|
23932
23932
|
const tmp = stringifyIndent(key2, value[key2], stack, spacer, indentation);
|
|
23933
23933
|
if (tmp !== void 0) {
|
|
23934
23934
|
res += `${separator}${strEscape(key2)}: ${tmp}`;
|
|
23935
|
-
separator =
|
|
23935
|
+
separator = join18;
|
|
23936
23936
|
}
|
|
23937
23937
|
}
|
|
23938
23938
|
if (keyLength > maximumBreadth) {
|
|
23939
23939
|
const removedKeys = keyLength - maximumBreadth;
|
|
23940
23940
|
res += `${separator}"...": "${getItemCount(removedKeys)} not stringified"`;
|
|
23941
|
-
separator =
|
|
23941
|
+
separator = join18;
|
|
23942
23942
|
}
|
|
23943
23943
|
if (separator !== "") {
|
|
23944
23944
|
res = `
|
|
@@ -31077,7 +31077,7 @@ var require_compile = __commonJS({
|
|
|
31077
31077
|
const schOrFunc = root.refs[ref];
|
|
31078
31078
|
if (schOrFunc)
|
|
31079
31079
|
return schOrFunc;
|
|
31080
|
-
let _sch =
|
|
31080
|
+
let _sch = resolve7.call(this, root, ref);
|
|
31081
31081
|
if (_sch === void 0) {
|
|
31082
31082
|
const schema = (_a = root.localRefs) === null || _a === void 0 ? void 0 : _a[ref];
|
|
31083
31083
|
const { schemaId } = this.opts;
|
|
@@ -31104,7 +31104,7 @@ var require_compile = __commonJS({
|
|
|
31104
31104
|
function sameSchemaEnv(s1, s2) {
|
|
31105
31105
|
return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
|
|
31106
31106
|
}
|
|
31107
|
-
function
|
|
31107
|
+
function resolve7(root, ref) {
|
|
31108
31108
|
let sch;
|
|
31109
31109
|
while (typeof (sch = this.refs[ref]) == "string")
|
|
31110
31110
|
ref = sch;
|
|
@@ -31215,14 +31215,14 @@ var require_utils = __commonJS({
|
|
|
31215
31215
|
BYTE_HEX[i] = "%" + HEX_DIGITS[i >> 4] + HEX_DIGITS[i & 15];
|
|
31216
31216
|
}
|
|
31217
31217
|
}
|
|
31218
|
-
function percentEncodeNonAscii(
|
|
31219
|
-
if (
|
|
31220
|
-
return BYTE_HEX[192 |
|
|
31218
|
+
function percentEncodeNonAscii(cp3) {
|
|
31219
|
+
if (cp3 < 2048) {
|
|
31220
|
+
return BYTE_HEX[192 | cp3 >> 6] + BYTE_HEX[128 | cp3 & 63];
|
|
31221
31221
|
}
|
|
31222
|
-
if (
|
|
31223
|
-
return BYTE_HEX[224 |
|
|
31222
|
+
if (cp3 < 65536) {
|
|
31223
|
+
return BYTE_HEX[224 | cp3 >> 12] + BYTE_HEX[128 | cp3 >> 6 & 63] + BYTE_HEX[128 | cp3 & 63];
|
|
31224
31224
|
}
|
|
31225
|
-
return BYTE_HEX[240 |
|
|
31225
|
+
return BYTE_HEX[240 | cp3 >> 18] + BYTE_HEX[128 | cp3 >> 12 & 63] + BYTE_HEX[128 | cp3 >> 6 & 63] + BYTE_HEX[128 | cp3 & 63];
|
|
31226
31226
|
}
|
|
31227
31227
|
function stringArrayToHexStripped(input) {
|
|
31228
31228
|
let acc = "";
|
|
@@ -31617,8 +31617,8 @@ var require_utils = __commonJS({
|
|
|
31617
31617
|
function encodeFragment(input) {
|
|
31618
31618
|
return encodeComponent(input, isQueryFragmentCharacter);
|
|
31619
31619
|
}
|
|
31620
|
-
function isEscapeSafe(
|
|
31621
|
-
return
|
|
31620
|
+
function isEscapeSafe(cp3) {
|
|
31621
|
+
return cp3 >= 48 && cp3 <= 57 || cp3 >= 65 && cp3 <= 90 || cp3 >= 97 && cp3 <= 122 || cp3 === 42 || cp3 === 43 || cp3 === 45 || cp3 === 46 || cp3 === 47 || cp3 === 64 || cp3 === 95;
|
|
31622
31622
|
}
|
|
31623
31623
|
function normalizeQueryFragmentEncoding(input) {
|
|
31624
31624
|
let output = "";
|
|
@@ -31968,7 +31968,7 @@ var require_fast_uri = __commonJS({
|
|
|
31968
31968
|
}
|
|
31969
31969
|
return uri;
|
|
31970
31970
|
}
|
|
31971
|
-
function
|
|
31971
|
+
function resolve7(baseURI, relativeURI, options) {
|
|
31972
31972
|
const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
|
|
31973
31973
|
const {
|
|
31974
31974
|
parsed: baseParsed,
|
|
@@ -32004,50 +32004,50 @@ var require_fast_uri = __commonJS({
|
|
|
32004
32004
|
schemelessOptions.skipEscape = true;
|
|
32005
32005
|
return serialize(resolved, schemelessOptions);
|
|
32006
32006
|
}
|
|
32007
|
-
function resolveComponent(base,
|
|
32007
|
+
function resolveComponent(base, relative5, options, skipNormalization) {
|
|
32008
32008
|
const target = {};
|
|
32009
32009
|
if (!skipNormalization) {
|
|
32010
32010
|
base = parse2(serialize(base, options), options);
|
|
32011
|
-
|
|
32011
|
+
relative5 = parse2(serialize(relative5, options), options);
|
|
32012
32012
|
}
|
|
32013
32013
|
options = options || {};
|
|
32014
|
-
if (!options.tolerant &&
|
|
32015
|
-
target.scheme =
|
|
32016
|
-
target.userinfo =
|
|
32017
|
-
target.host =
|
|
32018
|
-
target.port =
|
|
32019
|
-
target.path = removeDotSegments(
|
|
32020
|
-
target.query =
|
|
32014
|
+
if (!options.tolerant && relative5.scheme) {
|
|
32015
|
+
target.scheme = relative5.scheme;
|
|
32016
|
+
target.userinfo = relative5.userinfo;
|
|
32017
|
+
target.host = relative5.host;
|
|
32018
|
+
target.port = relative5.port;
|
|
32019
|
+
target.path = removeDotSegments(relative5.path || "");
|
|
32020
|
+
target.query = relative5.query;
|
|
32021
32021
|
} else {
|
|
32022
|
-
if (
|
|
32023
|
-
target.userinfo =
|
|
32024
|
-
target.host =
|
|
32025
|
-
target.port =
|
|
32026
|
-
target.path = removeDotSegments(
|
|
32027
|
-
target.query =
|
|
32022
|
+
if (relative5.userinfo !== void 0 || relative5.host !== void 0 || relative5.port !== void 0) {
|
|
32023
|
+
target.userinfo = relative5.userinfo;
|
|
32024
|
+
target.host = relative5.host;
|
|
32025
|
+
target.port = relative5.port;
|
|
32026
|
+
target.path = removeDotSegments(relative5.path || "");
|
|
32027
|
+
target.query = relative5.query;
|
|
32028
32028
|
} else {
|
|
32029
|
-
if (!
|
|
32029
|
+
if (!relative5.path) {
|
|
32030
32030
|
target.path = base.path;
|
|
32031
|
-
if (
|
|
32032
|
-
target.query =
|
|
32031
|
+
if (relative5.query !== void 0) {
|
|
32032
|
+
target.query = relative5.query;
|
|
32033
32033
|
} else {
|
|
32034
32034
|
target.query = base.query;
|
|
32035
32035
|
}
|
|
32036
32036
|
} else {
|
|
32037
|
-
if (
|
|
32038
|
-
target.path = removeDotSegments(
|
|
32037
|
+
if (relative5.path[0] === "/") {
|
|
32038
|
+
target.path = removeDotSegments(relative5.path);
|
|
32039
32039
|
} else {
|
|
32040
32040
|
const baseHasAuthority = base.userinfo !== void 0 || base.host !== void 0 || base.port !== void 0;
|
|
32041
32041
|
if (baseHasAuthority && !base.path) {
|
|
32042
|
-
target.path = "/" +
|
|
32042
|
+
target.path = "/" + relative5.path;
|
|
32043
32043
|
} else if (!base.path) {
|
|
32044
|
-
target.path =
|
|
32044
|
+
target.path = relative5.path;
|
|
32045
32045
|
} else {
|
|
32046
|
-
target.path = base.path.slice(0, base.path.lastIndexOf("/") + 1) +
|
|
32046
|
+
target.path = base.path.slice(0, base.path.lastIndexOf("/") + 1) + relative5.path;
|
|
32047
32047
|
}
|
|
32048
32048
|
target.path = base.scheme === void 0 && !baseHasAuthority && target.path[0] !== "/" ? removeDotSegmentsRelative(target.path) : removeDotSegments(target.path);
|
|
32049
32049
|
}
|
|
32050
|
-
target.query =
|
|
32050
|
+
target.query = relative5.query;
|
|
32051
32051
|
}
|
|
32052
32052
|
target.userinfo = base.userinfo;
|
|
32053
32053
|
target.host = base.host;
|
|
@@ -32055,7 +32055,7 @@ var require_fast_uri = __commonJS({
|
|
|
32055
32055
|
}
|
|
32056
32056
|
target.scheme = base.scheme;
|
|
32057
32057
|
}
|
|
32058
|
-
target.fragment =
|
|
32058
|
+
target.fragment = relative5.fragment;
|
|
32059
32059
|
return target;
|
|
32060
32060
|
}
|
|
32061
32061
|
function equal(uriA, uriB, options) {
|
|
@@ -32359,7 +32359,7 @@ var require_fast_uri = __commonJS({
|
|
|
32359
32359
|
var fastUri = {
|
|
32360
32360
|
SCHEMES,
|
|
32361
32361
|
normalize,
|
|
32362
|
-
resolve:
|
|
32362
|
+
resolve: resolve7,
|
|
32363
32363
|
resolveComponent,
|
|
32364
32364
|
equal,
|
|
32365
32365
|
serialize,
|
|
@@ -37023,17 +37023,17 @@ var require_utils2 = __commonJS({
|
|
|
37023
37023
|
BYTE_HEX[i] = "%" + HEX_DIGITS[i >> 4] + HEX_DIGITS[i & 15];
|
|
37024
37024
|
}
|
|
37025
37025
|
}
|
|
37026
|
-
function isEscapeSafe(
|
|
37027
|
-
return
|
|
37026
|
+
function isEscapeSafe(cp3) {
|
|
37027
|
+
return cp3 >= 48 && cp3 <= 57 || cp3 >= 65 && cp3 <= 90 || cp3 >= 97 && cp3 <= 122 || cp3 === 42 || cp3 === 43 || cp3 === 45 || cp3 === 46 || cp3 === 47 || cp3 === 64 || cp3 === 95;
|
|
37028
37028
|
}
|
|
37029
|
-
function percentEncodeNonAscii(
|
|
37030
|
-
if (
|
|
37031
|
-
return BYTE_HEX[192 |
|
|
37029
|
+
function percentEncodeNonAscii(cp3) {
|
|
37030
|
+
if (cp3 < 2048) {
|
|
37031
|
+
return BYTE_HEX[192 | cp3 >> 6] + BYTE_HEX[128 | cp3 & 63];
|
|
37032
37032
|
}
|
|
37033
|
-
if (
|
|
37034
|
-
return BYTE_HEX[224 |
|
|
37033
|
+
if (cp3 < 65536) {
|
|
37034
|
+
return BYTE_HEX[224 | cp3 >> 12] + BYTE_HEX[128 | cp3 >> 6 & 63] + BYTE_HEX[128 | cp3 & 63];
|
|
37035
37035
|
}
|
|
37036
|
-
return BYTE_HEX[240 |
|
|
37036
|
+
return BYTE_HEX[240 | cp3 >> 18] + BYTE_HEX[128 | cp3 >> 12 & 63] + BYTE_HEX[128 | cp3 >> 6 & 63] + BYTE_HEX[128 | cp3 & 63];
|
|
37037
37037
|
}
|
|
37038
37038
|
function normalizePathEncoding(input) {
|
|
37039
37039
|
let output = "";
|
|
@@ -37796,7 +37796,7 @@ var require_fast_uri2 = __commonJS({
|
|
|
37796
37796
|
}
|
|
37797
37797
|
return uri;
|
|
37798
37798
|
}
|
|
37799
|
-
function
|
|
37799
|
+
function resolve7(baseURI, relativeURI, options) {
|
|
37800
37800
|
const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
|
|
37801
37801
|
const {
|
|
37802
37802
|
parsed: baseParsed,
|
|
@@ -37846,51 +37846,51 @@ var require_fast_uri2 = __commonJS({
|
|
|
37846
37846
|
target.ipv6Zone = source.ipv6Zone;
|
|
37847
37847
|
}
|
|
37848
37848
|
}
|
|
37849
|
-
function resolveComponent(base,
|
|
37849
|
+
function resolveComponent(base, relative5, options, skipNormalization) {
|
|
37850
37850
|
const target = {};
|
|
37851
37851
|
if (!skipNormalization) {
|
|
37852
37852
|
base = parse2(serialize(base, options), options);
|
|
37853
|
-
|
|
37853
|
+
relative5 = parse2(serialize(relative5, options), options);
|
|
37854
37854
|
}
|
|
37855
37855
|
options = options || {};
|
|
37856
|
-
if (!options.tolerant &&
|
|
37857
|
-
target.scheme =
|
|
37858
|
-
target.userinfo =
|
|
37859
|
-
copyHost(target,
|
|
37860
|
-
target.port =
|
|
37861
|
-
const relativeSchemeHandler = getSchemeHandler(
|
|
37862
|
-
target.path = relativeSchemeHandler && relativeSchemeHandler.absolutePath ?
|
|
37863
|
-
target.query =
|
|
37856
|
+
if (!options.tolerant && relative5.scheme) {
|
|
37857
|
+
target.scheme = relative5.scheme;
|
|
37858
|
+
target.userinfo = relative5.userinfo;
|
|
37859
|
+
copyHost(target, relative5);
|
|
37860
|
+
target.port = relative5.port;
|
|
37861
|
+
const relativeSchemeHandler = getSchemeHandler(relative5.scheme);
|
|
37862
|
+
target.path = relativeSchemeHandler && relativeSchemeHandler.absolutePath ? relative5.path || "" : removeDotSegments(relative5.path || "");
|
|
37863
|
+
target.query = relative5.query;
|
|
37864
37864
|
} else {
|
|
37865
|
-
if (
|
|
37866
|
-
target.userinfo =
|
|
37867
|
-
copyHost(target,
|
|
37868
|
-
target.port =
|
|
37869
|
-
target.path = removeDotSegments(
|
|
37870
|
-
target.query =
|
|
37865
|
+
if (relative5.userinfo !== void 0 || relative5.host !== void 0 || relative5.port !== void 0) {
|
|
37866
|
+
target.userinfo = relative5.userinfo;
|
|
37867
|
+
copyHost(target, relative5);
|
|
37868
|
+
target.port = relative5.port;
|
|
37869
|
+
target.path = removeDotSegments(relative5.path || "");
|
|
37870
|
+
target.query = relative5.query;
|
|
37871
37871
|
} else {
|
|
37872
|
-
if (!
|
|
37872
|
+
if (!relative5.path) {
|
|
37873
37873
|
target.path = base.path;
|
|
37874
|
-
if (
|
|
37875
|
-
target.query =
|
|
37874
|
+
if (relative5.query !== void 0) {
|
|
37875
|
+
target.query = relative5.query;
|
|
37876
37876
|
} else {
|
|
37877
37877
|
target.query = base.query;
|
|
37878
37878
|
}
|
|
37879
37879
|
} else {
|
|
37880
|
-
if (
|
|
37881
|
-
target.path = removeDotSegments(
|
|
37880
|
+
if (relative5.path[0] === "/") {
|
|
37881
|
+
target.path = removeDotSegments(relative5.path);
|
|
37882
37882
|
} else {
|
|
37883
37883
|
const baseHasAuthority = base.userinfo !== void 0 || base.host !== void 0 || base.port !== void 0;
|
|
37884
37884
|
if (baseHasAuthority && !base.path) {
|
|
37885
|
-
target.path = "/" +
|
|
37885
|
+
target.path = "/" + relative5.path;
|
|
37886
37886
|
} else if (!base.path) {
|
|
37887
|
-
target.path =
|
|
37887
|
+
target.path = relative5.path;
|
|
37888
37888
|
} else {
|
|
37889
|
-
target.path = base.path.slice(0, base.path.lastIndexOf("/") + 1) +
|
|
37889
|
+
target.path = base.path.slice(0, base.path.lastIndexOf("/") + 1) + relative5.path;
|
|
37890
37890
|
}
|
|
37891
37891
|
target.path = base.scheme === void 0 && !baseHasAuthority && target.path[0] !== "/" ? removeDotSegmentsRelative(target.path) : removeDotSegments(target.path);
|
|
37892
37892
|
}
|
|
37893
|
-
target.query =
|
|
37893
|
+
target.query = relative5.query;
|
|
37894
37894
|
}
|
|
37895
37895
|
target.userinfo = base.userinfo;
|
|
37896
37896
|
copyHost(target, base);
|
|
@@ -37898,7 +37898,7 @@ var require_fast_uri2 = __commonJS({
|
|
|
37898
37898
|
}
|
|
37899
37899
|
target.scheme = base.scheme;
|
|
37900
37900
|
}
|
|
37901
|
-
target.fragment =
|
|
37901
|
+
target.fragment = relative5.fragment;
|
|
37902
37902
|
return target;
|
|
37903
37903
|
}
|
|
37904
37904
|
function equal(uriA, uriB, options) {
|
|
@@ -38223,7 +38223,7 @@ var require_fast_uri2 = __commonJS({
|
|
|
38223
38223
|
var fastUri = {
|
|
38224
38224
|
SCHEMES,
|
|
38225
38225
|
normalize,
|
|
38226
|
-
resolve:
|
|
38226
|
+
resolve: resolve7,
|
|
38227
38227
|
resolveComponent,
|
|
38228
38228
|
equal,
|
|
38229
38229
|
serialize,
|
|
@@ -49446,7 +49446,7 @@ var require_form_data = __commonJS({
|
|
|
49446
49446
|
"../../node_modules/.pnpm/light-my-request@6.8.0/node_modules/light-my-request/lib/form-data.js"(exports, module) {
|
|
49447
49447
|
"use strict";
|
|
49448
49448
|
var { randomUUID } = __require("node:crypto");
|
|
49449
|
-
var { Readable } = __require("node:stream");
|
|
49449
|
+
var { Readable: Readable2 } = __require("node:stream");
|
|
49450
49450
|
var textEncoder;
|
|
49451
49451
|
function isFormDataLike(payload) {
|
|
49452
49452
|
return payload && typeof payload === "object" && typeof payload.append === "function" && typeof payload.delete === "function" && typeof payload.get === "function" && typeof payload.getAll === "function" && typeof payload.has === "function" && typeof payload.set === "function" && payload[Symbol.toStringTag] === "FormData";
|
|
@@ -49485,7 +49485,7 @@ Content-Type: ${value.type || "application/octet-stream"}\r
|
|
|
49485
49485
|
}
|
|
49486
49486
|
yield textEncoder.encode(`--${boundary}--`);
|
|
49487
49487
|
}
|
|
49488
|
-
const stream =
|
|
49488
|
+
const stream = Readable2.from(asyncIterator());
|
|
49489
49489
|
return {
|
|
49490
49490
|
stream,
|
|
49491
49491
|
contentType: `multipart/form-data; boundary=${boundary}`
|
|
@@ -49500,7 +49500,7 @@ Content-Type: ${value.type || "application/octet-stream"}\r
|
|
|
49500
49500
|
var require_request2 = __commonJS({
|
|
49501
49501
|
"../../node_modules/.pnpm/light-my-request@6.8.0/node_modules/light-my-request/lib/request.js"(exports, module) {
|
|
49502
49502
|
"use strict";
|
|
49503
|
-
var { Readable, addAbortSignal } = __require("node:stream");
|
|
49503
|
+
var { Readable: Readable2, addAbortSignal } = __require("node:stream");
|
|
49504
49504
|
var util = __require("node:util");
|
|
49505
49505
|
var assert = __require("node:assert");
|
|
49506
49506
|
var { createDeprecation } = require_process_warning();
|
|
@@ -49567,7 +49567,7 @@ var require_request2 = __commonJS({
|
|
|
49567
49567
|
}
|
|
49568
49568
|
}
|
|
49569
49569
|
function Request2(options) {
|
|
49570
|
-
|
|
49570
|
+
Readable2.call(this, {
|
|
49571
49571
|
autoDestroy: false
|
|
49572
49572
|
});
|
|
49573
49573
|
const parsedURL = parseURL(options.url || options.path, options.query);
|
|
@@ -49724,7 +49724,7 @@ var require_request2 = __commonJS({
|
|
|
49724
49724
|
}
|
|
49725
49725
|
});
|
|
49726
49726
|
}
|
|
49727
|
-
util.inherits(Request2,
|
|
49727
|
+
util.inherits(Request2, Readable2);
|
|
49728
49728
|
util.inherits(CustomRequest, Request2);
|
|
49729
49729
|
Request2.prototype.destroy = function(error) {
|
|
49730
49730
|
if (this.destroyed || this._lightMyRequest.isDone) return;
|
|
@@ -49978,7 +49978,7 @@ var require_response = __commonJS({
|
|
|
49978
49978
|
"../../node_modules/.pnpm/light-my-request@6.8.0/node_modules/light-my-request/lib/response.js"(exports, module) {
|
|
49979
49979
|
"use strict";
|
|
49980
49980
|
var http = __require("node:http");
|
|
49981
|
-
var { Writable, Readable, addAbortSignal } = __require("node:stream");
|
|
49981
|
+
var { Writable, Readable: Readable2, addAbortSignal } = __require("node:stream");
|
|
49982
49982
|
var util = __require("node:util");
|
|
49983
49983
|
var setCookie = require_set_cookie();
|
|
49984
49984
|
function Response2(req, onEnd, reject) {
|
|
@@ -49986,7 +49986,7 @@ var require_response = __commonJS({
|
|
|
49986
49986
|
this.isLightMyRequest = true;
|
|
49987
49987
|
if (req._lightMyRequest?.payloadAsStream) {
|
|
49988
49988
|
const read = this.emit.bind(this, "drain");
|
|
49989
|
-
this._lightMyRequest = { headers: null, trailers: {}, stream: new
|
|
49989
|
+
this._lightMyRequest = { headers: null, trailers: {}, stream: new Readable2({ read }) };
|
|
49990
49990
|
const signal = req._lightMyRequest.signal;
|
|
49991
49991
|
if (signal) {
|
|
49992
49992
|
addAbortSignal(signal, this._lightMyRequest.stream);
|
|
@@ -50132,7 +50132,7 @@ var require_response = __commonJS({
|
|
|
50132
50132
|
if (response._lightMyRequest.stream) {
|
|
50133
50133
|
return response._lightMyRequest.stream;
|
|
50134
50134
|
}
|
|
50135
|
-
return
|
|
50135
|
+
return Readable2.from(response._lightMyRequest.payloadChunks);
|
|
50136
50136
|
};
|
|
50137
50137
|
return res;
|
|
50138
50138
|
}
|
|
@@ -51207,9 +51207,9 @@ var require_light_my_request = __commonJS({
|
|
|
51207
51207
|
const res = new Response2(req, callback);
|
|
51208
51208
|
return makeRequest(dispatchFunc, server, req, res);
|
|
51209
51209
|
} else {
|
|
51210
|
-
return new Promise((
|
|
51210
|
+
return new Promise((resolve7, reject) => {
|
|
51211
51211
|
const req = new RequestConstructor(options);
|
|
51212
|
-
const res = new Response2(req,
|
|
51212
|
+
const res = new Response2(req, resolve7, reject);
|
|
51213
51213
|
makeRequest(dispatchFunc, server, req, res);
|
|
51214
51214
|
});
|
|
51215
51215
|
}
|
|
@@ -52149,10 +52149,276 @@ ${body}`);
|
|
|
52149
52149
|
});
|
|
52150
52150
|
|
|
52151
52151
|
// src/cli.ts
|
|
52152
|
-
import { existsSync as
|
|
52153
|
-
import { delimiter as delimiter3, join as
|
|
52152
|
+
import { existsSync as existsSync9, readFileSync as readFileSync2 } from "node:fs";
|
|
52153
|
+
import { delimiter as delimiter3, join as join17, resolve as resolve6 } from "node:path";
|
|
52154
52154
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
52155
52155
|
|
|
52156
|
+
// ../../packages/core/src/image/png.ts
|
|
52157
|
+
import { crc32, deflateSync, inflateSync } from "node:zlib";
|
|
52158
|
+
var SIGNATURE = Uint8Array.from([137, 80, 78, 71, 13, 10, 26, 10]);
|
|
52159
|
+
var CHANNELS = { 0: 1, 2: 3, 4: 2, 6: 4 };
|
|
52160
|
+
var COLOR_TYPE = { 1: 0, 2: 4, 3: 2, 4: 6 };
|
|
52161
|
+
var PngError = class extends Error {
|
|
52162
|
+
name = "PngError";
|
|
52163
|
+
};
|
|
52164
|
+
function readUint32(bytes, offset) {
|
|
52165
|
+
return (bytes[offset] << 24 | bytes[offset + 1] << 16 | bytes[offset + 2] << 8 | bytes[offset + 3]) >>> 0;
|
|
52166
|
+
}
|
|
52167
|
+
function paeth(left, up, upLeft) {
|
|
52168
|
+
const estimate = left + up - upLeft;
|
|
52169
|
+
const toLeft = Math.abs(estimate - left);
|
|
52170
|
+
const toUp = Math.abs(estimate - up);
|
|
52171
|
+
const toUpLeft = Math.abs(estimate - upLeft);
|
|
52172
|
+
if (toLeft <= toUp && toLeft <= toUpLeft) return left;
|
|
52173
|
+
return toUp <= toUpLeft ? up : upLeft;
|
|
52174
|
+
}
|
|
52175
|
+
function decodePng(file) {
|
|
52176
|
+
if (file.length < 8 || SIGNATURE.some((byte, i) => file[i] !== byte)) throw new PngError("Not a PNG file");
|
|
52177
|
+
let offset = 8;
|
|
52178
|
+
let width = 0;
|
|
52179
|
+
let height = 0;
|
|
52180
|
+
let channels;
|
|
52181
|
+
const idat = [];
|
|
52182
|
+
while (offset + 8 <= file.length) {
|
|
52183
|
+
const length = readUint32(file, offset);
|
|
52184
|
+
const type = String.fromCharCode(file[offset + 4], file[offset + 5], file[offset + 6], file[offset + 7]);
|
|
52185
|
+
const body = file.subarray(offset + 8, offset + 8 + length);
|
|
52186
|
+
if (body.length !== length) throw new PngError("Truncated PNG chunk");
|
|
52187
|
+
if (type === "IHDR") {
|
|
52188
|
+
width = readUint32(body, 0);
|
|
52189
|
+
height = readUint32(body, 4);
|
|
52190
|
+
const bitDepth = body[8];
|
|
52191
|
+
const colorType = body[9];
|
|
52192
|
+
const interlace = body[12];
|
|
52193
|
+
if (bitDepth !== 8) throw new PngError(`Unsupported PNG bit depth ${bitDepth}`);
|
|
52194
|
+
if (interlace !== 0) throw new PngError("Interlaced PNGs are not supported");
|
|
52195
|
+
channels = CHANNELS[colorType];
|
|
52196
|
+
if (!channels) throw new PngError(`Unsupported PNG color type ${colorType}`);
|
|
52197
|
+
} else if (type === "IDAT") {
|
|
52198
|
+
idat.push(body);
|
|
52199
|
+
} else if (type === "IEND") {
|
|
52200
|
+
break;
|
|
52201
|
+
}
|
|
52202
|
+
offset += 12 + length;
|
|
52203
|
+
}
|
|
52204
|
+
if (!channels || width === 0 || height === 0) throw new PngError("PNG has no image header");
|
|
52205
|
+
const packed = inflateSync(Buffer.concat(idat));
|
|
52206
|
+
const stride = width * channels;
|
|
52207
|
+
if (packed.length < height * (stride + 1)) throw new PngError("PNG image data is too short");
|
|
52208
|
+
const data = new Uint8Array(height * stride);
|
|
52209
|
+
for (let y = 0; y < height; y++) {
|
|
52210
|
+
const filter = packed[y * (stride + 1)];
|
|
52211
|
+
const source = y * (stride + 1) + 1;
|
|
52212
|
+
const row = y * stride;
|
|
52213
|
+
const previous = row - stride;
|
|
52214
|
+
for (let x = 0; x < stride; x++) {
|
|
52215
|
+
const raw = packed[source + x];
|
|
52216
|
+
const left = x >= channels ? data[row + x - channels] : 0;
|
|
52217
|
+
const up = y > 0 ? data[previous + x] : 0;
|
|
52218
|
+
const upLeft = y > 0 && x >= channels ? data[previous + x - channels] : 0;
|
|
52219
|
+
let value;
|
|
52220
|
+
switch (filter) {
|
|
52221
|
+
case 0:
|
|
52222
|
+
value = raw;
|
|
52223
|
+
break;
|
|
52224
|
+
case 1:
|
|
52225
|
+
value = raw + left;
|
|
52226
|
+
break;
|
|
52227
|
+
case 2:
|
|
52228
|
+
value = raw + up;
|
|
52229
|
+
break;
|
|
52230
|
+
case 3:
|
|
52231
|
+
value = raw + (left + up >> 1);
|
|
52232
|
+
break;
|
|
52233
|
+
case 4:
|
|
52234
|
+
value = raw + paeth(left, up, upLeft);
|
|
52235
|
+
break;
|
|
52236
|
+
default:
|
|
52237
|
+
throw new PngError(`Unknown PNG filter ${filter}`);
|
|
52238
|
+
}
|
|
52239
|
+
data[row + x] = value & 255;
|
|
52240
|
+
}
|
|
52241
|
+
}
|
|
52242
|
+
return { width, height, channels, data };
|
|
52243
|
+
}
|
|
52244
|
+
function chunk(type, body) {
|
|
52245
|
+
const head = Buffer.alloc(8);
|
|
52246
|
+
head.writeUInt32BE(body.length, 0);
|
|
52247
|
+
head.write(type, 4, "latin1");
|
|
52248
|
+
const crc = Buffer.alloc(4);
|
|
52249
|
+
crc.writeUInt32BE(crc32(Buffer.concat([head.subarray(4), body])) >>> 0, 0);
|
|
52250
|
+
return Buffer.concat([head, body, crc]);
|
|
52251
|
+
}
|
|
52252
|
+
function encodePng(image) {
|
|
52253
|
+
const { width, height, channels, data } = image;
|
|
52254
|
+
const stride = width * channels;
|
|
52255
|
+
const filtered = Buffer.alloc(height * (stride + 1));
|
|
52256
|
+
for (let y = 0; y < height; y++) {
|
|
52257
|
+
filtered[y * (stride + 1)] = 2;
|
|
52258
|
+
for (let x = 0; x < stride; x++) {
|
|
52259
|
+
const up = y > 0 ? data[(y - 1) * stride + x] : 0;
|
|
52260
|
+
filtered[y * (stride + 1) + 1 + x] = data[y * stride + x] - up & 255;
|
|
52261
|
+
}
|
|
52262
|
+
}
|
|
52263
|
+
const header = Buffer.alloc(13);
|
|
52264
|
+
header.writeUInt32BE(width, 0);
|
|
52265
|
+
header.writeUInt32BE(height, 4);
|
|
52266
|
+
header[8] = 8;
|
|
52267
|
+
header[9] = COLOR_TYPE[channels];
|
|
52268
|
+
return Buffer.concat([
|
|
52269
|
+
Buffer.from(SIGNATURE),
|
|
52270
|
+
chunk("IHDR", header),
|
|
52271
|
+
chunk("IDAT", deflateSync(filtered)),
|
|
52272
|
+
chunk("IEND", new Uint8Array(0))
|
|
52273
|
+
]);
|
|
52274
|
+
}
|
|
52275
|
+
function resizeImage(image, width, height) {
|
|
52276
|
+
const { channels } = image;
|
|
52277
|
+
const data = new Uint8Array(width * height * channels);
|
|
52278
|
+
const sums = new Float64Array(channels);
|
|
52279
|
+
for (let y = 0; y < height; y++) {
|
|
52280
|
+
const y0 = Math.floor(y * image.height / height);
|
|
52281
|
+
const y1 = Math.max(y0 + 1, Math.floor((y + 1) * image.height / height));
|
|
52282
|
+
for (let x = 0; x < width; x++) {
|
|
52283
|
+
const x0 = Math.floor(x * image.width / width);
|
|
52284
|
+
const x1 = Math.max(x0 + 1, Math.floor((x + 1) * image.width / width));
|
|
52285
|
+
sums.fill(0);
|
|
52286
|
+
for (let sy = y0; sy < y1; sy++) {
|
|
52287
|
+
for (let sx = x0; sx < x1; sx++) {
|
|
52288
|
+
const at = (sy * image.width + sx) * channels;
|
|
52289
|
+
for (let c = 0; c < channels; c++) sums[c] += image.data[at + c];
|
|
52290
|
+
}
|
|
52291
|
+
}
|
|
52292
|
+
const count = (y1 - y0) * (x1 - x0);
|
|
52293
|
+
const out = (y * width + x) * channels;
|
|
52294
|
+
for (let c = 0; c < channels; c++) data[out + c] = Math.round(sums[c] / count);
|
|
52295
|
+
}
|
|
52296
|
+
}
|
|
52297
|
+
return { width, height, channels, data };
|
|
52298
|
+
}
|
|
52299
|
+
|
|
52300
|
+
// ../../packages/core/src/image/similarity.ts
|
|
52301
|
+
var DEFAULTS = {
|
|
52302
|
+
columns: 27,
|
|
52303
|
+
rows: 120,
|
|
52304
|
+
cropTop: 0.06,
|
|
52305
|
+
cropBottom: 0.05,
|
|
52306
|
+
tolerance: 14,
|
|
52307
|
+
maxShiftRows: 8
|
|
52308
|
+
};
|
|
52309
|
+
var LOOKS_DIFFERENT_BELOW = 85;
|
|
52310
|
+
function luminanceAt(image, index) {
|
|
52311
|
+
const { channels, data } = image;
|
|
52312
|
+
const at = index * channels;
|
|
52313
|
+
if (channels <= 2) return data[at];
|
|
52314
|
+
return 0.299 * data[at] + 0.587 * data[at + 1] + 0.114 * data[at + 2];
|
|
52315
|
+
}
|
|
52316
|
+
function luminanceGrid(image, options = {}) {
|
|
52317
|
+
const { columns, rows, cropTop, cropBottom } = { ...DEFAULTS, ...options };
|
|
52318
|
+
const top = Math.floor(image.height * cropTop);
|
|
52319
|
+
const bottom = Math.max(top + rows, Math.ceil(image.height * (1 - cropBottom)));
|
|
52320
|
+
const usable = Math.min(image.height, bottom) - top;
|
|
52321
|
+
const sums = new Float64Array(columns * rows);
|
|
52322
|
+
const counts = new Float64Array(columns * rows);
|
|
52323
|
+
for (let y = top; y < top + usable; y += 2) {
|
|
52324
|
+
const row = Math.min(rows - 1, Math.floor((y - top) * rows / usable));
|
|
52325
|
+
for (let x = 0; x < image.width; x += 2) {
|
|
52326
|
+
const column = Math.min(columns - 1, Math.floor(x * columns / image.width));
|
|
52327
|
+
const cell = row * columns + column;
|
|
52328
|
+
sums[cell] += luminanceAt(image, y * image.width + x);
|
|
52329
|
+
counts[cell] += 1;
|
|
52330
|
+
}
|
|
52331
|
+
}
|
|
52332
|
+
for (let i = 0; i < sums.length; i++) sums[i] = counts[i] > 0 ? sums[i] / counts[i] : 0;
|
|
52333
|
+
return sums;
|
|
52334
|
+
}
|
|
52335
|
+
function background(a, b) {
|
|
52336
|
+
const buckets = /* @__PURE__ */ new Map();
|
|
52337
|
+
for (const grid of [a, b]) {
|
|
52338
|
+
for (const value of grid) {
|
|
52339
|
+
const bucket = Math.round(value / 8);
|
|
52340
|
+
buckets.set(bucket, (buckets.get(bucket) ?? 0) + 1);
|
|
52341
|
+
}
|
|
52342
|
+
}
|
|
52343
|
+
let best = 0;
|
|
52344
|
+
let bestCount = -1;
|
|
52345
|
+
for (const [bucket, count] of buckets) {
|
|
52346
|
+
if (count > bestCount) {
|
|
52347
|
+
best = bucket;
|
|
52348
|
+
bestCount = count;
|
|
52349
|
+
}
|
|
52350
|
+
}
|
|
52351
|
+
return best * 8;
|
|
52352
|
+
}
|
|
52353
|
+
function scoreAt(gridA, gridB, columns, rows, shift, bg, tolerance) {
|
|
52354
|
+
let content = 0;
|
|
52355
|
+
let matched = 0;
|
|
52356
|
+
let cells = 0;
|
|
52357
|
+
let allMatched = 0;
|
|
52358
|
+
for (let row = Math.max(0, -shift); row < Math.min(rows, rows - shift); row++) {
|
|
52359
|
+
for (let column = 0; column < columns; column++) {
|
|
52360
|
+
const va = gridA[row * columns + column];
|
|
52361
|
+
const at = (row + shift) * columns + column;
|
|
52362
|
+
const vb = gridB[at];
|
|
52363
|
+
const up = row + shift > 0 ? gridB[at - columns] : vb;
|
|
52364
|
+
const down = row + shift < rows - 1 ? gridB[at + columns] : vb;
|
|
52365
|
+
const low = Math.min(up, vb, down) - tolerance;
|
|
52366
|
+
const high = Math.max(up, vb, down) + tolerance;
|
|
52367
|
+
const same = va >= low && va <= high;
|
|
52368
|
+
cells++;
|
|
52369
|
+
if (same) allMatched++;
|
|
52370
|
+
if (Math.abs(va - bg) > 10 || Math.abs(vb - bg) > 10) {
|
|
52371
|
+
content++;
|
|
52372
|
+
if (same) matched++;
|
|
52373
|
+
}
|
|
52374
|
+
}
|
|
52375
|
+
}
|
|
52376
|
+
if (content < cells * 0.02) return cells === 0 ? 0 : allMatched / cells;
|
|
52377
|
+
return matched / content;
|
|
52378
|
+
}
|
|
52379
|
+
function screenSimilarity(a, b, options = {}) {
|
|
52380
|
+
const { tolerance, columns, rows, maxShiftRows } = { ...DEFAULTS, ...options };
|
|
52381
|
+
const gridA = luminanceGrid(a, options);
|
|
52382
|
+
const gridB = luminanceGrid(b, options);
|
|
52383
|
+
const bg = background(gridA, gridB);
|
|
52384
|
+
let best = 0;
|
|
52385
|
+
for (let shift = -maxShiftRows; shift <= maxShiftRows; shift++) {
|
|
52386
|
+
best = Math.max(best, scoreAt(gridA, gridB, columns, rows, shift, bg, tolerance));
|
|
52387
|
+
}
|
|
52388
|
+
return Math.round(best * 1e3) / 10;
|
|
52389
|
+
}
|
|
52390
|
+
|
|
52391
|
+
// ../../packages/core/src/device/hidden-screen.ts
|
|
52392
|
+
var SYSTEM_UI = "com.android.systemui";
|
|
52393
|
+
var BLACK_BELOW = 2;
|
|
52394
|
+
function hasAppElements(nodes) {
|
|
52395
|
+
return nodes.some((node) => node.systemBar === void 0 && node.bundleId !== SYSTEM_UI);
|
|
52396
|
+
}
|
|
52397
|
+
function isBlackImage(image) {
|
|
52398
|
+
return luminanceGrid(image, { cropTop: 0, cropBottom: 0 }).every((cell) => cell < BLACK_BELOW);
|
|
52399
|
+
}
|
|
52400
|
+
function screenView(nodes, png) {
|
|
52401
|
+
let black = false;
|
|
52402
|
+
try {
|
|
52403
|
+
black = isBlackImage(decodePng(png));
|
|
52404
|
+
} catch {
|
|
52405
|
+
}
|
|
52406
|
+
if (!black) return "visible";
|
|
52407
|
+
return hasAppElements(nodes) ? "secure" : "hidden";
|
|
52408
|
+
}
|
|
52409
|
+
function parseKeyguardShowing(output) {
|
|
52410
|
+
const match = /isKeyguardShowing=(true|false)/.exec(output);
|
|
52411
|
+
return match ? match[1] === "true" : void 0;
|
|
52412
|
+
}
|
|
52413
|
+
function blackPicture(viewport) {
|
|
52414
|
+
const width = Math.max(1, Math.round(viewport.width / 10));
|
|
52415
|
+
const height = Math.max(1, Math.round(viewport.height / 10));
|
|
52416
|
+
return encodePng({ width, height, channels: 1, data: new Uint8Array(width * height) });
|
|
52417
|
+
}
|
|
52418
|
+
function looksLikeHomeApp(app) {
|
|
52419
|
+
return /launcher|\.home(\.|$)/i.test(app);
|
|
52420
|
+
}
|
|
52421
|
+
|
|
52156
52422
|
// ../../packages/core/src/device/hit-test.ts
|
|
52157
52423
|
function rectContains(rect, point) {
|
|
52158
52424
|
return point.x >= rect.x && point.y >= rect.y && point.x < rect.x + rect.width && point.y < rect.y + rect.height;
|
|
@@ -52211,15 +52477,15 @@ import { promisify as promisify3 } from "node:util";
|
|
|
52211
52477
|
|
|
52212
52478
|
// ../../packages/core/src/replay/maestro-driver.ts
|
|
52213
52479
|
import { execFile as execFile2 } from "node:child_process";
|
|
52214
|
-
import { mkdtemp, readFile, readdir, realpath, rm, writeFile } from "node:fs/promises";
|
|
52480
|
+
import { mkdtemp, readFile, readdir as readdir2, realpath, rm as rm2, writeFile } from "node:fs/promises";
|
|
52215
52481
|
import { tmpdir } from "node:os";
|
|
52216
52482
|
import { dirname as dirname2, join as join3 } from "node:path";
|
|
52217
52483
|
import { promisify as promisify2 } from "node:util";
|
|
52218
52484
|
|
|
52219
52485
|
// ../../packages/core/src/device/session.ts
|
|
52220
52486
|
import { execFile } from "node:child_process";
|
|
52221
|
-
import { existsSync as
|
|
52222
|
-
import { mkdir } from "node:fs/promises";
|
|
52487
|
+
import { existsSync as existsSync3 } from "node:fs";
|
|
52488
|
+
import { mkdir as mkdir2 } from "node:fs/promises";
|
|
52223
52489
|
import { homedir as homedir2 } from "node:os";
|
|
52224
52490
|
import { dirname, join as join2 } from "node:path";
|
|
52225
52491
|
import { promisify } from "node:util";
|
|
@@ -52230,35 +52496,132 @@ import {
|
|
|
52230
52496
|
import { AppError, isAgentDeviceError } from "agent-device";
|
|
52231
52497
|
|
|
52232
52498
|
// ../../packages/core/src/replay/maestro-bin.ts
|
|
52233
|
-
import { existsSync } from "node:fs";
|
|
52499
|
+
import { existsSync as existsSync2 } from "node:fs";
|
|
52234
52500
|
import { homedir } from "node:os";
|
|
52235
|
-
import { join, posix, win32 } from "node:path";
|
|
52501
|
+
import { join, posix, win32 as win322 } from "node:path";
|
|
52502
|
+
|
|
52503
|
+
// ../../packages/core/src/replay/shared-folder.ts
|
|
52504
|
+
import { execFileSync } from "node:child_process";
|
|
52505
|
+
import { existsSync } from "node:fs";
|
|
52506
|
+
import { mkdir, readdir, rm } from "node:fs/promises";
|
|
52507
|
+
import { win32 } from "node:path";
|
|
52508
|
+
var SYSTEM = "S-1-5-18";
|
|
52509
|
+
var ADMINISTRATORS = "S-1-5-32-544";
|
|
52510
|
+
var TRUSTED_INSTALLER = "S-1-5-80-956008885-3418522649-1831038044-1853292631-2271478464";
|
|
52511
|
+
var TRUSTED = /* @__PURE__ */ new Set(["SY", "BA", "LA", "CO", "OW", TRUSTED_INSTALLER]);
|
|
52512
|
+
var READ_ONLY_CODES = /* @__PURE__ */ new Set(["FR", "FX", "GR", "GX", "RC"]);
|
|
52513
|
+
var READ_ONLY_BITS = 2685534377;
|
|
52514
|
+
function onlyReads(rights) {
|
|
52515
|
+
if (/^0x[0-9a-f]+$/i.test(rights)) return (Number(rights) & ~READ_ONLY_BITS) === 0;
|
|
52516
|
+
const codes = rights.match(/../g) ?? [];
|
|
52517
|
+
return codes.length > 0 && codes.every((code) => READ_ONLY_CODES.has(code.toUpperCase()));
|
|
52518
|
+
}
|
|
52519
|
+
function onlyTrustedCanChange(sddl, me) {
|
|
52520
|
+
const trusted = (who) => who === me || TRUSTED.has(who);
|
|
52521
|
+
const owner = /^O:(.+?)(?:G:|D:|S:|$)/.exec(sddl)?.[1];
|
|
52522
|
+
const dacl = /D:([A-Z_]*)((?:\([^()]*\))*)(?:S:.*)?$/.exec(sddl);
|
|
52523
|
+
if (!owner || !trusted(owner) || !dacl || dacl[1].includes("NO_ACCESS_CONTROL")) return false;
|
|
52524
|
+
const entries = dacl[2].slice(1, -1).split(")(").filter(Boolean);
|
|
52525
|
+
return entries.every((entry) => {
|
|
52526
|
+
const [type = "", , rights = "", , , who = ""] = entry.split(";");
|
|
52527
|
+
return type === "D" || trusted(who) || onlyReads(rights);
|
|
52528
|
+
});
|
|
52529
|
+
}
|
|
52530
|
+
function runSystemTool(tool, args) {
|
|
52531
|
+
const file = win32.join(process.env.SystemRoot ?? "C:\\Windows", "System32", tool);
|
|
52532
|
+
const env = Object.fromEntries(Object.entries(process.env).filter(([name]) => name.toUpperCase() !== "PSMODULEPATH"));
|
|
52533
|
+
return execFileSync(file, args, { encoding: "utf8", env, timeout: 3e4, windowsHide: true, stdio: ["ignore", "pipe", "pipe"] });
|
|
52534
|
+
}
|
|
52535
|
+
function mySid() {
|
|
52536
|
+
const sid = runSystemTool("whoami.exe", ["/user", "/fo", "csv", "/nh"]).match(/S-1-\d+(?:-\d+)+/g)?.at(-1);
|
|
52537
|
+
if (!sid) throw new Error("whoami did not say who this is");
|
|
52538
|
+
return sid;
|
|
52539
|
+
}
|
|
52540
|
+
function folderSddl(dir) {
|
|
52541
|
+
const command = `(Get-Acl -LiteralPath '${dir.replaceAll("'", "''")}').Sddl`;
|
|
52542
|
+
return runSystemTool(win32.join("WindowsPowerShell", "v1.0", "powershell.exe"), ["-NoProfile", "-NonInteractive", "-Command", command]).replace(/\s+/g, "");
|
|
52543
|
+
}
|
|
52544
|
+
function folderIsPrivate(dir) {
|
|
52545
|
+
if (!existsSync(dir)) return true;
|
|
52546
|
+
try {
|
|
52547
|
+
return onlyTrustedCanChange(folderSddl(dir), mySid());
|
|
52548
|
+
} catch {
|
|
52549
|
+
return false;
|
|
52550
|
+
}
|
|
52551
|
+
}
|
|
52552
|
+
var privateFolders = /* @__PURE__ */ new Set();
|
|
52553
|
+
function sharedFolderSafe(dir) {
|
|
52554
|
+
if (privateFolders.has(dir)) return true;
|
|
52555
|
+
const safe = folderIsPrivate(dir);
|
|
52556
|
+
if (safe && existsSync(dir)) privateFolders.add(dir);
|
|
52557
|
+
return safe;
|
|
52558
|
+
}
|
|
52559
|
+
async function claimFolder(dir) {
|
|
52560
|
+
if (existsSync(dir) && folderIsPrivate(dir)) return;
|
|
52561
|
+
await rm(dir, { recursive: true, force: true, maxRetries: 3 });
|
|
52562
|
+
await mkdir(dir, { recursive: true });
|
|
52563
|
+
runSystemTool("icacls.exe", [dir, "/inheritance:r", "/grant:r", `*${mySid()}:(OI)(CI)F`, `*${SYSTEM}:(OI)(CI)F`, `*${ADMINISTRATORS}:(OI)(CI)F`]);
|
|
52564
|
+
const sddl = folderSddl(dir);
|
|
52565
|
+
if (!onlyTrustedCanChange(sddl, mySid()) || (await readdir(dir)).length > 0) throw new Error(`${dir} is still open to other accounts: ${sddl}`);
|
|
52566
|
+
}
|
|
52567
|
+
|
|
52568
|
+
// ../../packages/core/src/replay/maestro-bin.ts
|
|
52569
|
+
function isPlainAscii(text) {
|
|
52570
|
+
return /^[\x20-\x7e]*$/.test(text);
|
|
52571
|
+
}
|
|
52572
|
+
function toolsTarget(env = process.env, platform = process.platform) {
|
|
52573
|
+
if (platform !== "win32") return void 0;
|
|
52574
|
+
const base = [env.LOCALAPPDATA, env.ProgramData].find((dir) => dir && isPlainAscii(dir));
|
|
52575
|
+
return base ? win322.join(base, "Ariadne", "tools") : void 0;
|
|
52576
|
+
}
|
|
52577
|
+
function sharedToolsRoot(env = process.env, platform = process.platform) {
|
|
52578
|
+
if (platform !== "win32" || env.LOCALAPPDATA && isPlainAscii(env.LOCALAPPDATA)) return void 0;
|
|
52579
|
+
return env.ProgramData && isPlainAscii(env.ProgramData) ? win322.join(env.ProgramData, "Ariadne") : void 0;
|
|
52580
|
+
}
|
|
52581
|
+
function toolsDir(env = process.env, platform = process.platform, safe = sharedFolderSafe) {
|
|
52582
|
+
const shared = sharedToolsRoot(env, platform);
|
|
52583
|
+
return shared && !safe(shared) ? void 0 : toolsTarget(env, platform);
|
|
52584
|
+
}
|
|
52585
|
+
function toolsJavaHome(env = process.env, platform = process.platform) {
|
|
52586
|
+
const dir = toolsDir(env, platform);
|
|
52587
|
+
if (!dir) return void 0;
|
|
52588
|
+
const home = win322.join(dir, "java");
|
|
52589
|
+
return existsSync2(win322.join(home, "bin", "java.exe")) ? home : void 0;
|
|
52590
|
+
}
|
|
52236
52591
|
function maestroNames(platform) {
|
|
52237
52592
|
return platform === "win32" ? ["maestro.bat", "maestro.cmd", "maestro.exe"] : ["maestro"];
|
|
52238
52593
|
}
|
|
52239
52594
|
function maestroCandidates(platform, env, home) {
|
|
52240
|
-
const path = platform === "win32" ?
|
|
52595
|
+
const path = platform === "win32" ? win322 : posix;
|
|
52241
52596
|
const dirs = [path.join(home, ".maestro", "bin")];
|
|
52242
52597
|
if (platform === "win32") dirs.push(path.join(`${env.SystemDrive || "C:"}\\`, "maestro", "bin"));
|
|
52598
|
+
const tools = toolsDir(env, platform);
|
|
52599
|
+
if (tools) dirs.push(path.join(tools, "maestro", "bin"));
|
|
52243
52600
|
dirs.push(...(env.PATH ?? "").split(path.delimiter).filter(Boolean));
|
|
52244
52601
|
const candidates = env.MAESTRO_BIN ? [env.MAESTRO_BIN] : [];
|
|
52245
52602
|
for (const dir of dirs) for (const name of maestroNames(platform)) candidates.push(path.join(dir, name));
|
|
52246
52603
|
return candidates;
|
|
52247
52604
|
}
|
|
52248
52605
|
function findMaestro() {
|
|
52249
|
-
const found = maestroCandidates(process.platform, process.env, homedir()).find((candidate) =>
|
|
52606
|
+
const found = maestroCandidates(process.platform, process.env, homedir()).find((candidate) => existsSync2(candidate));
|
|
52250
52607
|
if (found) return found;
|
|
52251
52608
|
throw new Error(
|
|
52252
52609
|
process.platform === "win32" ? "Maestro CLI not found. Add the bin folder of the Maestro download to PATH, or set MAESTRO_BIN to its maestro.bat." : "Maestro CLI not found. Set MAESTRO_BIN or install it to ~/.maestro/bin/maestro."
|
|
52253
52610
|
);
|
|
52254
52611
|
}
|
|
52255
|
-
function
|
|
52256
|
-
const fromEnv = process.env.ANDROID_HOME || process.env.ANDROID_SDK_ROOT;
|
|
52257
|
-
if (fromEnv) return fromEnv;
|
|
52612
|
+
function sdkGuesses() {
|
|
52258
52613
|
const guesses = [join(homedir(), "Android", "Sdk")];
|
|
52259
52614
|
if (process.platform === "darwin") guesses.unshift(join(homedir(), "Library", "Android", "sdk"));
|
|
52260
52615
|
if (process.platform === "win32" && process.env.LOCALAPPDATA) guesses.unshift(join(process.env.LOCALAPPDATA, "Android", "Sdk"));
|
|
52261
|
-
return guesses
|
|
52616
|
+
return guesses;
|
|
52617
|
+
}
|
|
52618
|
+
function defaultSdkDir() {
|
|
52619
|
+
return sdkGuesses()[0];
|
|
52620
|
+
}
|
|
52621
|
+
function androidHome() {
|
|
52622
|
+
const fromEnv = process.env.ANDROID_HOME || process.env.ANDROID_SDK_ROOT;
|
|
52623
|
+
if (fromEnv) return fromEnv;
|
|
52624
|
+
return sdkGuesses().find((guess) => existsSync2(guess));
|
|
52262
52625
|
}
|
|
52263
52626
|
|
|
52264
52627
|
// ../../packages/core/src/device/session.ts
|
|
@@ -52267,7 +52630,7 @@ var LONG_PRESS_MS = 3e3;
|
|
|
52267
52630
|
function adbPath() {
|
|
52268
52631
|
const home = androidHome();
|
|
52269
52632
|
const candidate = home ? join2(home, "platform-tools", process.platform === "win32" ? "adb.exe" : "adb") : void 0;
|
|
52270
|
-
return candidate &&
|
|
52633
|
+
return candidate && existsSync3(candidate) ? candidate : "adb";
|
|
52271
52634
|
}
|
|
52272
52635
|
function defaultStateDir() {
|
|
52273
52636
|
return process.env.AGENT_DEVICE_STATE_DIR || join2(homedir2(), ".agent-device");
|
|
@@ -52414,7 +52777,7 @@ var DeviceSession = class {
|
|
|
52414
52777
|
*/
|
|
52415
52778
|
async capture(screenshotPath) {
|
|
52416
52779
|
const snapshot = await this.snapshot();
|
|
52417
|
-
await
|
|
52780
|
+
await mkdir2(dirname(screenshotPath), { recursive: true });
|
|
52418
52781
|
const shot = await this.client.capture.screenshot({ path: screenshotPath, scale: this.screenshotScale });
|
|
52419
52782
|
return { ...snapshot, screenshotPath: shot.path || screenshotPath };
|
|
52420
52783
|
}
|
|
@@ -52542,7 +52905,7 @@ var DeviceSession = class {
|
|
|
52542
52905
|
};
|
|
52543
52906
|
|
|
52544
52907
|
// ../../packages/core/src/export/zip.ts
|
|
52545
|
-
import { crc32, deflateRawSync, inflateRawSync } from "node:zlib";
|
|
52908
|
+
import { crc32 as crc322, deflateRawSync, inflateRawSync } from "node:zlib";
|
|
52546
52909
|
var DOS_TIME = 0;
|
|
52547
52910
|
var DOS_DATE = 0 << 9 | 1 << 5 | 1;
|
|
52548
52911
|
var END_OF_DIRECTORY = 101010256;
|
|
@@ -52582,7 +52945,7 @@ function zip(entries) {
|
|
|
52582
52945
|
const name = Buffer.from(entry.name, "utf8");
|
|
52583
52946
|
const raw = typeof entry.data === "string" ? Buffer.from(entry.data, "utf8") : Buffer.from(entry.data);
|
|
52584
52947
|
const compressed = deflateRawSync(raw);
|
|
52585
|
-
const crc =
|
|
52948
|
+
const crc = crc322(raw) >>> 0;
|
|
52586
52949
|
const local = Buffer.alloc(30);
|
|
52587
52950
|
local.writeUInt32LE(67324752, 0);
|
|
52588
52951
|
local.writeUInt16LE(20, 4);
|
|
@@ -52630,7 +52993,7 @@ var DriverInstallRefused = class extends Error {
|
|
|
52630
52993
|
};
|
|
52631
52994
|
async function clientJar(maestroBin) {
|
|
52632
52995
|
const lib = join3(dirname2(await realpath(maestroBin)), "..", "lib");
|
|
52633
|
-
const name = (await
|
|
52996
|
+
const name = (await readdir2(lib)).find((file) => /^maestro-client.*\.jar$/.test(file));
|
|
52634
52997
|
if (!name) throw new Error(`No maestro-client jar in ${lib}`);
|
|
52635
52998
|
return join3(lib, name);
|
|
52636
52999
|
}
|
|
@@ -52656,7 +53019,7 @@ async function installMaestroDriver(deviceId, maestroBin) {
|
|
|
52656
53019
|
await installApk(deviceId, path);
|
|
52657
53020
|
}
|
|
52658
53021
|
} finally {
|
|
52659
|
-
await
|
|
53022
|
+
await rm2(dir, { recursive: true, force: true });
|
|
52660
53023
|
}
|
|
52661
53024
|
}
|
|
52662
53025
|
|
|
@@ -52845,14 +53208,56 @@ async function readPhoneReport(shell2) {
|
|
|
52845
53208
|
};
|
|
52846
53209
|
}
|
|
52847
53210
|
|
|
53211
|
+
// ../../packages/core/src/device/tested-on.ts
|
|
53212
|
+
var PACKAGE_NAME = /^[A-Za-z][A-Za-z0-9_]*(\.[A-Za-z0-9_]+)+$/;
|
|
53213
|
+
var defined2 = (value) => {
|
|
53214
|
+
const trimmed = value?.trim();
|
|
53215
|
+
return trimmed && trimmed !== "null" ? trimmed : void 0;
|
|
53216
|
+
};
|
|
53217
|
+
function parseAppVersion(dumpsys) {
|
|
53218
|
+
const versionCode = defined2(/^\s*versionCode=(\d+)/m.exec(dumpsys)?.[1]);
|
|
53219
|
+
const versionName = defined2(/^\s*versionName=(.*)$/m.exec(dumpsys)?.[1]);
|
|
53220
|
+
return { ...versionName ? { versionName } : {}, ...versionCode ? { versionCode } : {} };
|
|
53221
|
+
}
|
|
53222
|
+
var EMULATOR_HARDWARE = /* @__PURE__ */ new Set(["ranchu", "goldfish", "vbox86"]);
|
|
53223
|
+
var EMULATOR_MAKERS = /* @__PURE__ */ new Set(["genymotion", "genymobile"]);
|
|
53224
|
+
function appVersionText(app) {
|
|
53225
|
+
if (app.versionName && app.versionCode) return `${app.versionName} (${app.versionCode})`;
|
|
53226
|
+
return app.versionName ?? app.versionCode;
|
|
53227
|
+
}
|
|
53228
|
+
async function readTestedOn(shell2, appIds) {
|
|
53229
|
+
const read = (...args) => shell2(args).then((output) => output, () => "");
|
|
53230
|
+
const [maker, model, android, kernelQemu, bootQemu, hardware, characteristics] = await Promise.all(
|
|
53231
|
+
["ro.product.manufacturer", "ro.product.model", "ro.build.version.release", "ro.kernel.qemu", "ro.boot.qemu", "ro.hardware", "ro.build.characteristics"].map(
|
|
53232
|
+
async (name) => defined2(await read("getprop", name))
|
|
53233
|
+
)
|
|
53234
|
+
);
|
|
53235
|
+
const emulatorSign = kernelQemu === "1" || bootQemu === "1" || hardware !== void 0 && EMULATOR_HARDWARE.has(hardware) || maker !== void 0 && EMULATOR_MAKERS.has(maker.toLowerCase()) || // A comma-separated list; emulator images built from Android's own sources say "emulator" in it.
|
|
53236
|
+
(characteristics?.split(",").some((part) => part.trim() === "emulator") ?? false);
|
|
53237
|
+
const emulator = emulatorSign ? true : hardware !== void 0 ? false : void 0;
|
|
53238
|
+
const apps = [];
|
|
53239
|
+
for (const appId of new Set(appIds)) {
|
|
53240
|
+
const version2 = PACKAGE_NAME.test(appId) ? parseAppVersion(await read("dumpsys", "package", appId)) : {};
|
|
53241
|
+
apps.push({ appId, ...version2 });
|
|
53242
|
+
}
|
|
53243
|
+
return {
|
|
53244
|
+
...maker ? { maker } : {},
|
|
53245
|
+
...model ? { model } : {},
|
|
53246
|
+
...android ? { android } : {},
|
|
53247
|
+
...emulator !== void 0 ? { emulator } : {},
|
|
53248
|
+
apps
|
|
53249
|
+
};
|
|
53250
|
+
}
|
|
53251
|
+
|
|
52848
53252
|
// ../../packages/core/src/device/phone-input.ts
|
|
52849
53253
|
var execFileAsync3 = promisify3(execFile3);
|
|
52850
53254
|
var ADB_TIMEOUT_MS = 1e4;
|
|
53255
|
+
var ADB_MAX_OUTPUT = 16 * 1024 * 1024;
|
|
52851
53256
|
var DEFAULT_LONG_PRESS_MS = 400;
|
|
52852
53257
|
var TOUCH_SLOP_DP = 8;
|
|
52853
53258
|
var NAVIGATION_MODE_GESTURES2 = "2";
|
|
52854
53259
|
async function shell(deviceId, ...args) {
|
|
52855
|
-
const { stdout } = await execFileAsync3(adbPath(), ["-s", deviceId, "shell", ...args], { timeout: ADB_TIMEOUT_MS, windowsHide: true });
|
|
53260
|
+
const { stdout } = await execFileAsync3(adbPath(), ["-s", deviceId, "shell", ...args], { timeout: ADB_TIMEOUT_MS, maxBuffer: ADB_MAX_OUTPUT, windowsHide: true });
|
|
52856
53261
|
return stdout;
|
|
52857
53262
|
}
|
|
52858
53263
|
async function readPhoneInput(deviceId) {
|
|
@@ -52896,6 +53301,9 @@ var phoneInputTools = {
|
|
|
52896
53301
|
xiaomi: (deviceId) => readXiaomiPhone((args) => shell(deviceId, ...args)),
|
|
52897
53302
|
allowDriver: (deviceId) => allowDriverToOpenApps((args) => shell(deviceId, ...args)),
|
|
52898
53303
|
report: (deviceId) => readPhoneReport((args) => shell(deviceId, ...args)),
|
|
53304
|
+
testedOn: (deviceId, appIds) => readTestedOn((args) => shell(deviceId, ...args), appIds),
|
|
53305
|
+
// Filtered on the phone: the rest of the dump names what is on screen, notifications too.
|
|
53306
|
+
locked: async (deviceId) => parseKeyguardShowing(await shell(deviceId, "dumpsys window | grep isKeyguardShowing=")),
|
|
52899
53307
|
stopDriver: (deviceId) => stopDriver((args) => shell(deviceId, ...args)),
|
|
52900
53308
|
installDriver: installMaestroDriver
|
|
52901
53309
|
};
|
|
@@ -52947,6 +53355,20 @@ function unescapeMaestroRegex(value) {
|
|
|
52947
53355
|
return value.replace(/\\([.*+?^${}()|[\]\\])/g, "$1");
|
|
52948
53356
|
}
|
|
52949
53357
|
|
|
53358
|
+
// ../../packages/core/src/maestro/model.ts
|
|
53359
|
+
var MAESTRO_KEYS = ["Home", "Back", "Enter", "Backspace", "Tab", "Escape", "Volume Up", "Volume Down", "Power", "Lock"];
|
|
53360
|
+
var MAESTRO_ORIENTATIONS = ["PORTRAIT", "LANDSCAPE_LEFT", "LANDSCAPE_RIGHT", "UPSIDE_DOWN"];
|
|
53361
|
+
var VARIABLE = /\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g;
|
|
53362
|
+
function flowVariables(flow) {
|
|
53363
|
+
const names = /* @__PURE__ */ new Set();
|
|
53364
|
+
for (const match of JSON.stringify(flow.steps).matchAll(VARIABLE)) names.add(match[1]);
|
|
53365
|
+
return [...names].sort();
|
|
53366
|
+
}
|
|
53367
|
+
var NEVER_ON_SCREEN = "ariadne-wait";
|
|
53368
|
+
function waitName(seconds) {
|
|
53369
|
+
return `Wait ${seconds} ${seconds === 1 ? "second" : "seconds"}`;
|
|
53370
|
+
}
|
|
53371
|
+
|
|
52950
53372
|
// ../../packages/core/src/recorder/block-values.ts
|
|
52951
53373
|
var SECRET_NAME = /(PASS|PWD|SECRET|TOKEN|PIN|OTP|API_?KEY|PRIVATE)/i;
|
|
52952
53374
|
var REFERENCE = /^\$\{([A-Za-z_][A-Za-z0-9_]*)\}$/;
|
|
@@ -52961,6 +53383,10 @@ function referencedKey(value) {
|
|
|
52961
53383
|
function reference(key) {
|
|
52962
53384
|
return `\${${key}}`;
|
|
52963
53385
|
}
|
|
53386
|
+
var PASSWORD_NAME = /pass(word|wd|code|phrase)|(^|_)pass(_|$)/i;
|
|
53387
|
+
function passwordSetting(keys) {
|
|
53388
|
+
return keys.includes("PASSWORD") ? "PASSWORD" : [...keys].sort().find((key) => PASSWORD_NAME.test(key)) ?? "PASSWORD";
|
|
53389
|
+
}
|
|
52964
53390
|
function label(prefix) {
|
|
52965
53391
|
return prefix.split("_").filter(Boolean).map((word) => word[0].toUpperCase() + word.slice(1).toLowerCase()).join(" ");
|
|
52966
53392
|
}
|
|
@@ -53033,6 +53459,8 @@ function readableId(id) {
|
|
|
53033
53459
|
return last.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_\-.]+/g, " ").trim().toLowerCase();
|
|
53034
53460
|
}
|
|
53035
53461
|
function elementName(selector) {
|
|
53462
|
+
const setting = selector.kind === "point" ? null : ENV_PLACEHOLDER.exec(selector.value);
|
|
53463
|
+
if (setting) return `the element the ${setting[1]} setting names`;
|
|
53036
53464
|
switch (selector.kind) {
|
|
53037
53465
|
case "text": {
|
|
53038
53466
|
const nth = selector.index === void 0 ? "" : ` (number ${selector.index + 1} on screen)`;
|
|
@@ -53112,6 +53540,8 @@ function describeStep(step, scrollTarget) {
|
|
|
53112
53540
|
return "Restart the app";
|
|
53113
53541
|
case "openLink":
|
|
53114
53542
|
return `Open the link ${step.url}`;
|
|
53543
|
+
case "wait":
|
|
53544
|
+
return waitName(step.seconds);
|
|
53115
53545
|
case "hideKeyboard":
|
|
53116
53546
|
return "Hide the keyboard";
|
|
53117
53547
|
case "erase":
|
|
@@ -53163,6 +53593,8 @@ function describeMaestroStep(step) {
|
|
|
53163
53593
|
return `Check that ${elementName(step.selector)} is not visible`;
|
|
53164
53594
|
case "openLink":
|
|
53165
53595
|
return `Open the link ${step.link}`;
|
|
53596
|
+
case "wait":
|
|
53597
|
+
return waitName(step.seconds);
|
|
53166
53598
|
case "takeScreenshot":
|
|
53167
53599
|
return "Take a screenshot";
|
|
53168
53600
|
case "startRecording":
|
|
@@ -53226,6 +53658,7 @@ function caseDocument(testCase, names = {}, lastRun) {
|
|
|
53226
53658
|
createdAt: testCase.createdAt,
|
|
53227
53659
|
...testCase.editedAt ? { editedAt: testCase.editedAt } : {},
|
|
53228
53660
|
...testCase.muted ? { muted: true } : {},
|
|
53661
|
+
...testCase.reference ? { reference: testCase.reference } : {},
|
|
53229
53662
|
preconditions,
|
|
53230
53663
|
steps,
|
|
53231
53664
|
endChecks: testCase.expectations.map(describeExpectation)
|
|
@@ -53259,6 +53692,7 @@ function caseMarkdown(doc, imagePath) {
|
|
|
53259
53692
|
lines.push(`# ${doc.name}`, "");
|
|
53260
53693
|
lines.push("| | |", "|---|---|");
|
|
53261
53694
|
lines.push(`| Case id | \`${doc.id}\` |`);
|
|
53695
|
+
if (doc.reference) lines.push(`| Reference | ${mdCell(doc.reference)} |`);
|
|
53262
53696
|
lines.push(`| App | \`${doc.appId}\` |`);
|
|
53263
53697
|
lines.push(`| Recorded | ${doc.createdAt.slice(0, 10)} |`);
|
|
53264
53698
|
if (doc.editedAt) lines.push(`| Last edited | ${doc.editedAt.slice(0, 10)} |`);
|
|
@@ -53289,12 +53723,13 @@ function caseMarkdown(doc, imagePath) {
|
|
|
53289
53723
|
return lines.join("\n");
|
|
53290
53724
|
}
|
|
53291
53725
|
function casesSheets(docs) {
|
|
53292
|
-
const overview = [["Case", "Case id", "App", "Preconditions", "Steps", "Expected at the end", "Last run", "Muted"]];
|
|
53726
|
+
const overview = [["Case", "Case id", "Reference", "App", "Preconditions", "Steps", "Expected at the end", "Last run", "Muted"]];
|
|
53293
53727
|
const steps = [["Case", "Step", "Action", "Expected result", "Screenshot"]];
|
|
53294
53728
|
for (const doc of docs) {
|
|
53295
53729
|
overview.push([
|
|
53296
53730
|
doc.name,
|
|
53297
53731
|
doc.id,
|
|
53732
|
+
doc.reference ?? "",
|
|
53298
53733
|
doc.appId,
|
|
53299
53734
|
doc.preconditions.join("\n"),
|
|
53300
53735
|
String(doc.steps.length),
|
|
@@ -53307,11 +53742,72 @@ function casesSheets(docs) {
|
|
|
53307
53742
|
if (doc.endChecks.length > 0) steps.push([doc.name, "End", "Check the final screen", doc.endChecks.join("\n"), ""]);
|
|
53308
53743
|
}
|
|
53309
53744
|
return [
|
|
53310
|
-
{ name: "Cases", rows: overview, widths: [36, 34, 26, 44, 8, 40, 40, 8] },
|
|
53745
|
+
{ name: "Cases", rows: overview, widths: [36, 34, 16, 26, 44, 8, 40, 40, 8] },
|
|
53311
53746
|
{ name: "Steps", rows: steps, widths: [36, 6, 54, 44, 44] }
|
|
53312
53747
|
];
|
|
53313
53748
|
}
|
|
53314
53749
|
|
|
53750
|
+
// ../../packages/core/src/export/report.ts
|
|
53751
|
+
function looksDifferent(similarity) {
|
|
53752
|
+
return similarity !== void 0 && similarity < LOOKS_DIFFERENT_BELOW;
|
|
53753
|
+
}
|
|
53754
|
+
function runReportRows(testCase, run2, names = {}) {
|
|
53755
|
+
const results = new Map(run2.steps.map((step) => [step.flowIndex, step]));
|
|
53756
|
+
return caseFlowItems(testCase).map((item, flowIndex) => {
|
|
53757
|
+
const result = results.get(flowIndex);
|
|
53758
|
+
const recorded = item.kind === "step" ? item.step.screenshot : void 0;
|
|
53759
|
+
return {
|
|
53760
|
+
number: flowIndex + 1,
|
|
53761
|
+
kind: item.kind === "step" ? item.step.kind : item.kind === "expectation" ? "end" : item.kind,
|
|
53762
|
+
label: describeFlowItem(item, testCase.appId, names),
|
|
53763
|
+
...result ? { status: result.status } : {},
|
|
53764
|
+
...result?.error ? { error: result.error } : {},
|
|
53765
|
+
...result?.durationMs !== void 0 ? { durationMs: result.durationMs } : {},
|
|
53766
|
+
...result?.startedAt !== void 0 ? { startedAt: result.startedAt } : {},
|
|
53767
|
+
...recorded ? { recorded } : {},
|
|
53768
|
+
...result?.replayScreenshot ? { replayed: result.replayScreenshot } : {},
|
|
53769
|
+
...result?.similarity !== void 0 ? { similarity: result.similarity } : {},
|
|
53770
|
+
looksDifferent: looksDifferent(result?.similarity)
|
|
53771
|
+
};
|
|
53772
|
+
});
|
|
53773
|
+
}
|
|
53774
|
+
|
|
53775
|
+
// ../../packages/core/src/export/evidence.ts
|
|
53776
|
+
var OUTCOME_TEXT = {
|
|
53777
|
+
passed: "Passed",
|
|
53778
|
+
flaky: "Passed when run again",
|
|
53779
|
+
failed: "Failed",
|
|
53780
|
+
error: "Could not run",
|
|
53781
|
+
muted: "Not run: muted",
|
|
53782
|
+
pending: "Not run: Run all was stopped",
|
|
53783
|
+
running: "Not run: Run all was stopped"
|
|
53784
|
+
};
|
|
53785
|
+
function outcomeText(outcome) {
|
|
53786
|
+
return OUTCOME_TEXT[outcome];
|
|
53787
|
+
}
|
|
53788
|
+
function evidenceSteps(testCase, run2, names = {}) {
|
|
53789
|
+
const items = caseFlowItems(testCase);
|
|
53790
|
+
return runReportRows(testCase, run2, names).map((row, index) => {
|
|
53791
|
+
const item = items[index];
|
|
53792
|
+
const expected = item?.kind === "step" ? stepExpected(item.step) : item?.kind === "expectation" ? describeExpectation(item.expectation) : "";
|
|
53793
|
+
return { ...row, expected };
|
|
53794
|
+
});
|
|
53795
|
+
}
|
|
53796
|
+
function evidenceScreen(step, mode, failureScreenshot) {
|
|
53797
|
+
const failed = step.status === "failed";
|
|
53798
|
+
if (mode === "checks" && !failed && step.kind !== "expect" && step.kind !== "end") return void 0;
|
|
53799
|
+
return step.replayed ?? (failed ? failureScreenshot : void 0);
|
|
53800
|
+
}
|
|
53801
|
+
var pad = (value) => String(value).padStart(2, "0");
|
|
53802
|
+
function localTimeText(iso) {
|
|
53803
|
+
const date = new Date(iso);
|
|
53804
|
+
if (Number.isNaN(date.getTime())) return iso;
|
|
53805
|
+
const offset = -date.getTimezoneOffset();
|
|
53806
|
+
const sign = offset < 0 ? "-" : "+";
|
|
53807
|
+
const zone = `UTC${sign}${pad(Math.floor(Math.abs(offset) / 60))}:${pad(Math.abs(offset) % 60)}`;
|
|
53808
|
+
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())} (${zone})`;
|
|
53809
|
+
}
|
|
53810
|
+
|
|
53315
53811
|
// ../../packages/core/src/export/html.ts
|
|
53316
53812
|
function escapeHtml(value) {
|
|
53317
53813
|
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
@@ -53343,14 +53839,14 @@ img.shot { width: 150px; border: 1px solid #dcdfe6; border-radius: 4px; display:
|
|
|
53343
53839
|
footer { margin-top: 28px; color: #8a93a3; font-size: 12px; }
|
|
53344
53840
|
`;
|
|
53345
53841
|
var BRAND = `<header class="brand"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="20" height="20" aria-hidden="true"><rect width="64" height="64" rx="14" fill="#1c2b4a"/><path d="M14 20 V50 H50 V14 H26 V38 H38 V26" fill="none" stroke="#e8b43a" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/><circle cx="14" cy="14" r="4.5" fill="none" stroke="#e8b43a" stroke-width="3.5"/><circle cx="38" cy="26" r="5" fill="#e8b43a"/></svg><span>Ariadne</span></header>`;
|
|
53346
|
-
function page(title, body) {
|
|
53842
|
+
function page(title, body, style = "") {
|
|
53347
53843
|
return `<!doctype html>
|
|
53348
53844
|
<html lang="en">
|
|
53349
53845
|
<head>
|
|
53350
53846
|
<meta charset="utf-8">
|
|
53351
53847
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
53352
53848
|
<title>${escapeHtml(title)}</title>
|
|
53353
|
-
<style>${STYLE}</style>
|
|
53849
|
+
<style>${STYLE}${style}</style>
|
|
53354
53850
|
</head>
|
|
53355
53851
|
<body>
|
|
53356
53852
|
${BRAND}
|
|
@@ -53372,7 +53868,7 @@ function caseSection(doc, image) {
|
|
|
53372
53868
|
const status = doc.lastRun ? doc.lastRun.passed ? "passed" : "failed" : "not-run";
|
|
53373
53869
|
return `<section class="case">
|
|
53374
53870
|
<h1>${escapeHtml(doc.name)}</h1>
|
|
53375
|
-
<div class="meta">Case <code>${escapeHtml(doc.id)}</code
|
|
53871
|
+
<div class="meta">Case <code>${escapeHtml(doc.id)}</code>${doc.reference ? ` / reference ${escapeHtml(doc.reference)}` : ""} / app <code>${escapeHtml(doc.appId)}</code> / recorded ${escapeHtml(doc.createdAt.slice(0, 10))}${doc.editedAt ? ` / edited ${escapeHtml(doc.editedAt.slice(0, 10))}` : ""}${doc.muted ? ' / <span class="muted">muted</span>' : ""}</div>
|
|
53376
53872
|
<div>Last run: <span class="${status}">${escapeHtml(lastRunText(doc))}</span></div>
|
|
53377
53873
|
<h2>Preconditions</h2>
|
|
53378
53874
|
<ol>${pre}</ol>
|
|
@@ -53426,121 +53922,123 @@ function suiteReportHtml(suite, cases, image) {
|
|
|
53426
53922
|
<table><thead><tr><th>Case</th><th>Result</th><th>Details</th><th>Screen</th></tr></thead><tbody>${rows}</tbody></table>`
|
|
53427
53923
|
);
|
|
53428
53924
|
}
|
|
53429
|
-
|
|
53430
|
-
|
|
53431
|
-
|
|
53432
|
-
|
|
53433
|
-
|
|
53434
|
-
|
|
53435
|
-
|
|
53436
|
-
|
|
53437
|
-
|
|
53438
|
-
}
|
|
53439
|
-
|
|
53440
|
-
|
|
53441
|
-
|
|
53442
|
-
|
|
53443
|
-
|
|
53444
|
-
|
|
53445
|
-
|
|
53446
|
-
|
|
53447
|
-
|
|
53448
|
-
|
|
53449
|
-
|
|
53450
|
-
|
|
53451
|
-
|
|
53452
|
-
|
|
53453
|
-
|
|
53454
|
-
|
|
53455
|
-
|
|
53456
|
-
|
|
53457
|
-
|
|
53458
|
-
|
|
53459
|
-
|
|
53460
|
-
}
|
|
53461
|
-
}
|
|
53462
|
-
|
|
53463
|
-
|
|
53464
|
-
}
|
|
53465
|
-
|
|
53466
|
-
|
|
53467
|
-
|
|
53468
|
-
|
|
53469
|
-
|
|
53470
|
-
|
|
53471
|
-
|
|
53472
|
-
|
|
53473
|
-
|
|
53474
|
-
|
|
53475
|
-
|
|
53476
|
-
|
|
53477
|
-
|
|
53478
|
-
|
|
53479
|
-
|
|
53480
|
-
|
|
53481
|
-
|
|
53482
|
-
}
|
|
53483
|
-
function scoreAt(gridA, gridB, columns, rows, shift, bg, tolerance) {
|
|
53484
|
-
let content = 0;
|
|
53485
|
-
let matched = 0;
|
|
53486
|
-
let cells = 0;
|
|
53487
|
-
let allMatched = 0;
|
|
53488
|
-
for (let row = Math.max(0, -shift); row < Math.min(rows, rows - shift); row++) {
|
|
53489
|
-
for (let column = 0; column < columns; column++) {
|
|
53490
|
-
const va = gridA[row * columns + column];
|
|
53491
|
-
const at = (row + shift) * columns + column;
|
|
53492
|
-
const vb = gridB[at];
|
|
53493
|
-
const up = row + shift > 0 ? gridB[at - columns] : vb;
|
|
53494
|
-
const down = row + shift < rows - 1 ? gridB[at + columns] : vb;
|
|
53495
|
-
const low = Math.min(up, vb, down) - tolerance;
|
|
53496
|
-
const high = Math.max(up, vb, down) + tolerance;
|
|
53497
|
-
const same = va >= low && va <= high;
|
|
53498
|
-
cells++;
|
|
53499
|
-
if (same) allMatched++;
|
|
53500
|
-
if (Math.abs(va - bg) > 10 || Math.abs(vb - bg) > 10) {
|
|
53501
|
-
content++;
|
|
53502
|
-
if (same) matched++;
|
|
53503
|
-
}
|
|
53504
|
-
}
|
|
53505
|
-
}
|
|
53506
|
-
if (content < cells * 0.02) return cells === 0 ? 0 : allMatched / cells;
|
|
53507
|
-
return matched / content;
|
|
53508
|
-
}
|
|
53509
|
-
function screenSimilarity(a, b, options = {}) {
|
|
53510
|
-
const { tolerance, columns, rows, maxShiftRows } = { ...DEFAULTS, ...options };
|
|
53511
|
-
const gridA = luminanceGrid(a, options);
|
|
53512
|
-
const gridB = luminanceGrid(b, options);
|
|
53513
|
-
const bg = background(gridA, gridB);
|
|
53514
|
-
let best = 0;
|
|
53515
|
-
for (let shift = -maxShiftRows; shift <= maxShiftRows; shift++) {
|
|
53516
|
-
best = Math.max(best, scoreAt(gridA, gridB, columns, rows, shift, bg, tolerance));
|
|
53517
|
-
}
|
|
53518
|
-
return Math.round(best * 1e3) / 10;
|
|
53519
|
-
}
|
|
53520
|
-
|
|
53521
|
-
// ../../packages/core/src/export/report.ts
|
|
53522
|
-
function looksDifferent(similarity) {
|
|
53523
|
-
return similarity !== void 0 && similarity < LOOKS_DIFFERENT_BELOW;
|
|
53925
|
+
var EVIDENCE_STYLE = `
|
|
53926
|
+
.cover { page-break-after: always; }
|
|
53927
|
+
table.facts { width: auto; min-width: 60%; }
|
|
53928
|
+
table.facts th { width: 130px; }
|
|
53929
|
+
table.signoff th:first-child { width: 110px; }
|
|
53930
|
+
table.signoff td { height: 44px; }
|
|
53931
|
+
.case h2 { margin-top: 0; }
|
|
53932
|
+
.written { white-space: pre-wrap; }
|
|
53933
|
+
.hash { font-size: 11px; word-break: break-all; }
|
|
53934
|
+
.print-button { margin-bottom: 16px; padding: 6px 14px; font: inherit; cursor: pointer; }
|
|
53935
|
+
@media print { a { color: inherit; text-decoration: none; } .print-button { display: none; } tr { page-break-inside: avoid; } }
|
|
53936
|
+
`;
|
|
53937
|
+
function deviceText(testedOn) {
|
|
53938
|
+
if (!testedOn) return void 0;
|
|
53939
|
+
const name = [testedOn.maker, testedOn.model].filter(Boolean).join(" ");
|
|
53940
|
+
const android = testedOn.android ? `Android ${testedOn.android}` : "";
|
|
53941
|
+
const text = [name, android].filter(Boolean).join(", ");
|
|
53942
|
+
const kind = testedOn.emulator === true ? "Emulator" : testedOn.emulator === false ? "Real phone" : "";
|
|
53943
|
+
if (!text) return kind || void 0;
|
|
53944
|
+
return kind ? `${kind}: ${text}` : text;
|
|
53945
|
+
}
|
|
53946
|
+
function recordLine(record) {
|
|
53947
|
+
return `<code>${escapeHtml(record.path)}</code><div class="hash">SHA-256 <code>${escapeHtml(record.sha256)}</code></div>`;
|
|
53948
|
+
}
|
|
53949
|
+
function evidenceCaseSection(item, number, image, time, screens) {
|
|
53950
|
+
const run2 = item.run;
|
|
53951
|
+
const anyFailedRow = item.steps.some((step) => step.status === "failed");
|
|
53952
|
+
const rows = item.steps.map((step) => {
|
|
53953
|
+
const status = step.status ?? "not-run";
|
|
53954
|
+
const shotPath = evidenceScreen(step, screens, run2?.failureScreenshot);
|
|
53955
|
+
const shot = shotPath ? image(shotPath) : void 0;
|
|
53956
|
+
return `<tr><td class="num">${step.number}</td><td>${escapeHtml(step.label)}${step.error ? `<div class="error">${escapeHtml(step.error)}</div>` : ""}</td><td>${escapeHtml(step.expected)}</td><td class="${status}">${status === "not-run" ? "not run" : status}${step.looksDifferent ? '<div class="warn">looks different from the recording</div>' : ""}</td><td>${img(shot, `Screen after step ${step.number}`)}</td></tr>`;
|
|
53957
|
+
}).join("");
|
|
53958
|
+
const failure = run2?.failureScreenshot && !anyFailedRow ? image(run2.failureScreenshot) : void 0;
|
|
53959
|
+
const meta = [
|
|
53960
|
+
`Case <code>${escapeHtml(item.caseId)}</code>`,
|
|
53961
|
+
...item.reference ? [`reference ${escapeHtml(item.reference)}`] : [],
|
|
53962
|
+
...item.appId ? [`app <code>${escapeHtml(item.appId)}</code>`] : [],
|
|
53963
|
+
...run2 ? [`run <code>${escapeHtml(run2.runId)}</code> at ${escapeHtml(time(run2.startedAt))}`] : [],
|
|
53964
|
+
...item.attempts > 1 ? [`${item.attempts} runs`] : []
|
|
53965
|
+
].join(" / ");
|
|
53966
|
+
return `<section class="case" id="case-${number}">
|
|
53967
|
+
<h2>${number}. ${escapeHtml(item.name)}</h2>
|
|
53968
|
+
<div class="meta">${meta}</div>
|
|
53969
|
+
${item.record ? `<div class="meta">Results from ${recordLine(item.record)}</div>` : ""}
|
|
53970
|
+
<p>Result: <span class="${item.outcome}">${escapeHtml(outcomeText(item.outcome))}</span></p>
|
|
53971
|
+
${item.stale ? '<p class="warn">The case was edited after this run. The steps below are the case as it is now, so they may not line up with what ran.</p>' : ""}
|
|
53972
|
+
${item.missing ? '<p class="warn">The case was deleted after Run all, so its steps are not shown.</p>' : ""}
|
|
53973
|
+
${item.note ? `<p class="meta">${escapeHtml(item.note)}</p>` : ""}
|
|
53974
|
+
${run2?.error ? `<p class="error">${escapeHtml(run2.error)}</p>` : ""}
|
|
53975
|
+
${failure ? `<h3>Screen when it failed</h3>${img(failure, "Screen when the run failed")}` : ""}
|
|
53976
|
+
${rows ? `<table><thead><tr><th>#</th><th>Step</th><th>Expected result</th><th>Result</th><th>Screen after</th></tr></thead><tbody>${rows}</tbody></table>` : ""}
|
|
53977
|
+
</section>`;
|
|
53524
53978
|
}
|
|
53525
|
-
function
|
|
53526
|
-
const
|
|
53527
|
-
|
|
53528
|
-
|
|
53529
|
-
|
|
53530
|
-
|
|
53531
|
-
|
|
53532
|
-
|
|
53533
|
-
|
|
53534
|
-
|
|
53535
|
-
|
|
53536
|
-
|
|
53537
|
-
|
|
53538
|
-
|
|
53539
|
-
|
|
53540
|
-
|
|
53541
|
-
|
|
53542
|
-
|
|
53543
|
-
}
|
|
53979
|
+
function evidenceHtml(input) {
|
|
53980
|
+
const { suite, cases, image } = input;
|
|
53981
|
+
const time = input.time ?? localTimeText;
|
|
53982
|
+
const screens = input.screens ?? "all";
|
|
53983
|
+
const release2 = input.release?.trim() ?? "";
|
|
53984
|
+
const testedBy = input.testedBy?.trim() ?? "";
|
|
53985
|
+
const signoffRole = input.signoffRole?.trim() || "Accepted by";
|
|
53986
|
+
const caseApps = [...new Set(cases.map((item) => item.appId).filter((appId) => Boolean(appId)))];
|
|
53987
|
+
const apps = suite.testedOn?.apps.length ? suite.testedOn.apps : caseApps.map((appId) => ({ appId }));
|
|
53988
|
+
const appLines = apps.map((app) => `<code>${escapeHtml(app.appId)}</code> ${escapeHtml(appVersionText(app) ?? "version not read")}`).join("<br>");
|
|
53989
|
+
const counts = /* @__PURE__ */ new Map();
|
|
53990
|
+
for (const item of cases) {
|
|
53991
|
+
const text = outcomeText(item.outcome).toLowerCase();
|
|
53992
|
+
counts.set(text, (counts.get(text) ?? 0) + 1);
|
|
53993
|
+
}
|
|
53994
|
+
const tally = [...counts].map(([text, count]) => `${count} ${text}`).join(", ");
|
|
53995
|
+
const passed = suite.status === "finished" && cases.some((item) => item.outcome === "passed" || item.outcome === "flaky") && cases.every((item) => ["passed", "flaky", "muted"].includes(item.outcome));
|
|
53996
|
+
const failedCount = cases.filter((item) => item.outcome === "failed" || item.outcome === "error").length;
|
|
53997
|
+
const verdict = passed ? "Every case that ran passed." : failedCount > 0 ? `${failedCount} of ${cases.length} cases did not pass.` : "Not every case ran.";
|
|
53998
|
+
const withSection = new Set(cases.filter((item) => item.steps.length > 0 || item.run).map((item) => item.caseId));
|
|
53999
|
+
const withReference = cases.some((item) => item.reference);
|
|
54000
|
+
const summaryRows = cases.map((item, index) => {
|
|
54001
|
+
const number = index + 1;
|
|
54002
|
+
const name = withSection.has(item.caseId) ? `<a href="#case-${number}">${escapeHtml(item.name)}</a>` : escapeHtml(item.name);
|
|
54003
|
+
const problems = [item.failedStep ? `Failed at: ${item.failedStep}` : "", item.error ?? ""].filter(Boolean).join("\n");
|
|
54004
|
+
const notes = [item.note ?? "", item.stale ? "The case was edited after this run." : "", item.missing ? "The case was deleted after Run all." : ""].filter(Boolean).join("\n");
|
|
54005
|
+
return `<tr><td class="num">${number}</td>${withReference ? `<td>${escapeHtml(item.reference ?? "")}</td>` : ""}<td>${name}</td><td class="${item.outcome}">${escapeHtml(outcomeText(item.outcome))}</td><td>${problems ? `<div class="error">${escapeHtml(problems)}</div>` : ""}${notes ? `<div class="meta">${escapeHtml(notes)}</div>` : ""}</td></tr>`;
|
|
54006
|
+
}).join("");
|
|
54007
|
+
const runText = `${time(suite.startedAt)} to ${suite.finishedAt ? time(suite.finishedAt) : "not finished"}`;
|
|
54008
|
+
const facts = [
|
|
54009
|
+
["Release", escapeHtml(release2)],
|
|
54010
|
+
[apps.length > 1 ? "Apps" : "App", appLines || "none"],
|
|
54011
|
+
["Device", escapeHtml(deviceText(suite.testedOn) ?? "not read")],
|
|
54012
|
+
["Run", `${escapeHtml(runText)}<div class="meta">Run all <code>${escapeHtml(suite.suiteId)}</code></div>`],
|
|
54013
|
+
["Failures", suite.retryFailed ? "A case that failed ran once more. If it passed then, it shows as passed when run again." : "Each case ran once."],
|
|
54014
|
+
["Screens", screens === "checks" ? "After checks and after a step that failed." : "After every step."],
|
|
54015
|
+
["Tested by", escapeHtml(testedBy)],
|
|
54016
|
+
["Result", `<span class="${passed ? "passed" : "failed"}">${escapeHtml(verdict)}</span><div class="meta">${escapeHtml(tally || "No cases.")}</div>`],
|
|
54017
|
+
["Known issues", `<div class="written">${escapeHtml(input.knownIssues?.trim() ?? "")}</div>`],
|
|
54018
|
+
["Conclusion", `<div class="written">${escapeHtml(input.conclusion?.trim() ?? "")}</div>`],
|
|
54019
|
+
...input.record ? [["Record", recordLine(input.record)]] : []
|
|
54020
|
+
];
|
|
54021
|
+
const factRows = facts.map(([label2, value]) => `<tr><th>${label2}</th><td>${value}</td></tr>`).join("");
|
|
54022
|
+
const stopped = suite.status === "stopped" ? '<p class="warn">Run all was stopped before the end.</p>' : suite.status === "running" ? '<p class="warn">Run all was still running when this was made.</p>' : "";
|
|
54023
|
+
const sections = cases.map((item, index) => withSection.has(item.caseId) ? evidenceCaseSection(item, index + 1, image, time, screens) : "").filter(Boolean).join("\n");
|
|
54024
|
+
return page(
|
|
54025
|
+
`Test results report${release2 ? `: ${release2}` : ""}`,
|
|
54026
|
+
`<button type="button" class="print-button" onclick="window.print()">Print or save as PDF</button>
|
|
54027
|
+
<section class="cover">
|
|
54028
|
+
<h1>Test results report</h1>
|
|
54029
|
+
<table class="facts"><tbody>${factRows}</tbody></table>
|
|
54030
|
+
${stopped}
|
|
54031
|
+
<h2>Cases</h2>
|
|
54032
|
+
${cases.length === 0 ? "<p>No cases.</p>" : `<table><thead><tr><th>#</th>${withReference ? "<th>Reference</th>" : ""}<th>Case</th><th>Result</th><th>Details</th></tr></thead><tbody>${summaryRows}</tbody></table>`}
|
|
54033
|
+
<h2>Sign-off</h2>
|
|
54034
|
+
<table class="signoff"><thead><tr><th></th><th>Name</th><th>Date</th><th>Signature</th></tr></thead><tbody>
|
|
54035
|
+
<tr><th>Tested by</th><td>${escapeHtml(testedBy)}</td><td></td><td></td></tr>
|
|
54036
|
+
<tr><th>${escapeHtml(signoffRole)}</th><td></td><td></td><td></td></tr>
|
|
54037
|
+
</tbody></table>
|
|
54038
|
+
</section>
|
|
54039
|
+
${sections}`,
|
|
54040
|
+
EVIDENCE_STYLE
|
|
54041
|
+
);
|
|
53544
54042
|
}
|
|
53545
54043
|
|
|
53546
54044
|
// ../../packages/core/src/export/xlsx.ts
|
|
@@ -53608,165 +54106,18 @@ function buildXlsx(sheets) {
|
|
|
53608
54106
|
]);
|
|
53609
54107
|
}
|
|
53610
54108
|
|
|
53611
|
-
// ../../packages/core/src/image/png.ts
|
|
53612
|
-
import { crc32 as crc322, deflateSync, inflateSync } from "node:zlib";
|
|
53613
|
-
var SIGNATURE = Uint8Array.from([137, 80, 78, 71, 13, 10, 26, 10]);
|
|
53614
|
-
var CHANNELS = { 0: 1, 2: 3, 4: 2, 6: 4 };
|
|
53615
|
-
var COLOR_TYPE = { 1: 0, 2: 4, 3: 2, 4: 6 };
|
|
53616
|
-
var PngError = class extends Error {
|
|
53617
|
-
name = "PngError";
|
|
53618
|
-
};
|
|
53619
|
-
function readUint32(bytes, offset) {
|
|
53620
|
-
return (bytes[offset] << 24 | bytes[offset + 1] << 16 | bytes[offset + 2] << 8 | bytes[offset + 3]) >>> 0;
|
|
53621
|
-
}
|
|
53622
|
-
function paeth(left, up, upLeft) {
|
|
53623
|
-
const estimate = left + up - upLeft;
|
|
53624
|
-
const toLeft = Math.abs(estimate - left);
|
|
53625
|
-
const toUp = Math.abs(estimate - up);
|
|
53626
|
-
const toUpLeft = Math.abs(estimate - upLeft);
|
|
53627
|
-
if (toLeft <= toUp && toLeft <= toUpLeft) return left;
|
|
53628
|
-
return toUp <= toUpLeft ? up : upLeft;
|
|
53629
|
-
}
|
|
53630
|
-
function decodePng(file) {
|
|
53631
|
-
if (file.length < 8 || SIGNATURE.some((byte, i) => file[i] !== byte)) throw new PngError("Not a PNG file");
|
|
53632
|
-
let offset = 8;
|
|
53633
|
-
let width = 0;
|
|
53634
|
-
let height = 0;
|
|
53635
|
-
let channels;
|
|
53636
|
-
const idat = [];
|
|
53637
|
-
while (offset + 8 <= file.length) {
|
|
53638
|
-
const length = readUint32(file, offset);
|
|
53639
|
-
const type = String.fromCharCode(file[offset + 4], file[offset + 5], file[offset + 6], file[offset + 7]);
|
|
53640
|
-
const body = file.subarray(offset + 8, offset + 8 + length);
|
|
53641
|
-
if (body.length !== length) throw new PngError("Truncated PNG chunk");
|
|
53642
|
-
if (type === "IHDR") {
|
|
53643
|
-
width = readUint32(body, 0);
|
|
53644
|
-
height = readUint32(body, 4);
|
|
53645
|
-
const bitDepth = body[8];
|
|
53646
|
-
const colorType = body[9];
|
|
53647
|
-
const interlace = body[12];
|
|
53648
|
-
if (bitDepth !== 8) throw new PngError(`Unsupported PNG bit depth ${bitDepth}`);
|
|
53649
|
-
if (interlace !== 0) throw new PngError("Interlaced PNGs are not supported");
|
|
53650
|
-
channels = CHANNELS[colorType];
|
|
53651
|
-
if (!channels) throw new PngError(`Unsupported PNG color type ${colorType}`);
|
|
53652
|
-
} else if (type === "IDAT") {
|
|
53653
|
-
idat.push(body);
|
|
53654
|
-
} else if (type === "IEND") {
|
|
53655
|
-
break;
|
|
53656
|
-
}
|
|
53657
|
-
offset += 12 + length;
|
|
53658
|
-
}
|
|
53659
|
-
if (!channels || width === 0 || height === 0) throw new PngError("PNG has no image header");
|
|
53660
|
-
const packed = inflateSync(Buffer.concat(idat));
|
|
53661
|
-
const stride = width * channels;
|
|
53662
|
-
if (packed.length < height * (stride + 1)) throw new PngError("PNG image data is too short");
|
|
53663
|
-
const data = new Uint8Array(height * stride);
|
|
53664
|
-
for (let y = 0; y < height; y++) {
|
|
53665
|
-
const filter = packed[y * (stride + 1)];
|
|
53666
|
-
const source = y * (stride + 1) + 1;
|
|
53667
|
-
const row = y * stride;
|
|
53668
|
-
const previous = row - stride;
|
|
53669
|
-
for (let x = 0; x < stride; x++) {
|
|
53670
|
-
const raw = packed[source + x];
|
|
53671
|
-
const left = x >= channels ? data[row + x - channels] : 0;
|
|
53672
|
-
const up = y > 0 ? data[previous + x] : 0;
|
|
53673
|
-
const upLeft = y > 0 && x >= channels ? data[previous + x - channels] : 0;
|
|
53674
|
-
let value;
|
|
53675
|
-
switch (filter) {
|
|
53676
|
-
case 0:
|
|
53677
|
-
value = raw;
|
|
53678
|
-
break;
|
|
53679
|
-
case 1:
|
|
53680
|
-
value = raw + left;
|
|
53681
|
-
break;
|
|
53682
|
-
case 2:
|
|
53683
|
-
value = raw + up;
|
|
53684
|
-
break;
|
|
53685
|
-
case 3:
|
|
53686
|
-
value = raw + (left + up >> 1);
|
|
53687
|
-
break;
|
|
53688
|
-
case 4:
|
|
53689
|
-
value = raw + paeth(left, up, upLeft);
|
|
53690
|
-
break;
|
|
53691
|
-
default:
|
|
53692
|
-
throw new PngError(`Unknown PNG filter ${filter}`);
|
|
53693
|
-
}
|
|
53694
|
-
data[row + x] = value & 255;
|
|
53695
|
-
}
|
|
53696
|
-
}
|
|
53697
|
-
return { width, height, channels, data };
|
|
53698
|
-
}
|
|
53699
|
-
function chunk(type, body) {
|
|
53700
|
-
const head = Buffer.alloc(8);
|
|
53701
|
-
head.writeUInt32BE(body.length, 0);
|
|
53702
|
-
head.write(type, 4, "latin1");
|
|
53703
|
-
const crc = Buffer.alloc(4);
|
|
53704
|
-
crc.writeUInt32BE(crc322(Buffer.concat([head.subarray(4), body])) >>> 0, 0);
|
|
53705
|
-
return Buffer.concat([head, body, crc]);
|
|
53706
|
-
}
|
|
53707
|
-
function encodePng(image) {
|
|
53708
|
-
const { width, height, channels, data } = image;
|
|
53709
|
-
const stride = width * channels;
|
|
53710
|
-
const filtered = Buffer.alloc(height * (stride + 1));
|
|
53711
|
-
for (let y = 0; y < height; y++) {
|
|
53712
|
-
filtered[y * (stride + 1)] = 2;
|
|
53713
|
-
for (let x = 0; x < stride; x++) {
|
|
53714
|
-
const up = y > 0 ? data[(y - 1) * stride + x] : 0;
|
|
53715
|
-
filtered[y * (stride + 1) + 1 + x] = data[y * stride + x] - up & 255;
|
|
53716
|
-
}
|
|
53717
|
-
}
|
|
53718
|
-
const header = Buffer.alloc(13);
|
|
53719
|
-
header.writeUInt32BE(width, 0);
|
|
53720
|
-
header.writeUInt32BE(height, 4);
|
|
53721
|
-
header[8] = 8;
|
|
53722
|
-
header[9] = COLOR_TYPE[channels];
|
|
53723
|
-
return Buffer.concat([
|
|
53724
|
-
Buffer.from(SIGNATURE),
|
|
53725
|
-
chunk("IHDR", header),
|
|
53726
|
-
chunk("IDAT", deflateSync(filtered)),
|
|
53727
|
-
chunk("IEND", new Uint8Array(0))
|
|
53728
|
-
]);
|
|
53729
|
-
}
|
|
53730
|
-
function resizeImage(image, width, height) {
|
|
53731
|
-
const { channels } = image;
|
|
53732
|
-
const data = new Uint8Array(width * height * channels);
|
|
53733
|
-
const sums = new Float64Array(channels);
|
|
53734
|
-
for (let y = 0; y < height; y++) {
|
|
53735
|
-
const y0 = Math.floor(y * image.height / height);
|
|
53736
|
-
const y1 = Math.max(y0 + 1, Math.floor((y + 1) * image.height / height));
|
|
53737
|
-
for (let x = 0; x < width; x++) {
|
|
53738
|
-
const x0 = Math.floor(x * image.width / width);
|
|
53739
|
-
const x1 = Math.max(x0 + 1, Math.floor((x + 1) * image.width / width));
|
|
53740
|
-
sums.fill(0);
|
|
53741
|
-
for (let sy = y0; sy < y1; sy++) {
|
|
53742
|
-
for (let sx = x0; sx < x1; sx++) {
|
|
53743
|
-
const at = (sy * image.width + sx) * channels;
|
|
53744
|
-
for (let c = 0; c < channels; c++) sums[c] += image.data[at + c];
|
|
53745
|
-
}
|
|
53746
|
-
}
|
|
53747
|
-
const count = (y1 - y0) * (x1 - x0);
|
|
53748
|
-
const out = (y * width + x) * channels;
|
|
53749
|
-
for (let c = 0; c < channels; c++) data[out + c] = Math.round(sums[c] / count);
|
|
53750
|
-
}
|
|
53751
|
-
}
|
|
53752
|
-
return { width, height, channels, data };
|
|
53753
|
-
}
|
|
53754
|
-
|
|
53755
54109
|
// ../../packages/core/src/maestro/block-parse.ts
|
|
53756
54110
|
var import_yaml2 = __toESM(require_dist(), 1);
|
|
53757
54111
|
|
|
53758
|
-
// ../../packages/core/src/maestro/model.ts
|
|
53759
|
-
var MAESTRO_KEYS = ["Home", "Back", "Enter", "Backspace", "Tab", "Escape", "Volume Up", "Volume Down", "Power", "Lock"];
|
|
53760
|
-
var MAESTRO_ORIENTATIONS = ["PORTRAIT", "LANDSCAPE_LEFT", "LANDSCAPE_RIGHT", "UPSIDE_DOWN"];
|
|
53761
|
-
var VARIABLE = /\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g;
|
|
53762
|
-
function flowVariables(flow) {
|
|
53763
|
-
const names = /* @__PURE__ */ new Set();
|
|
53764
|
-
for (const match of JSON.stringify(flow.steps).matchAll(VARIABLE)) names.add(match[1]);
|
|
53765
|
-
return [...names].sort();
|
|
53766
|
-
}
|
|
53767
|
-
|
|
53768
54112
|
// ../../packages/core/src/maestro/parse.ts
|
|
53769
54113
|
var import_yaml = __toESM(require_dist(), 1);
|
|
54114
|
+
var LONGEST_TIMER_MS = 2147483647;
|
|
54115
|
+
function isWaitForm(arg) {
|
|
54116
|
+
if (!isRecord(arg) || Object.keys(arg).some((key) => !["visible", "timeout", "optional", "label"].includes(key))) return false;
|
|
54117
|
+
const { visible, timeout, optional, label: label2 } = arg;
|
|
54118
|
+
if (visible !== NEVER_ON_SCREEN || optional !== true || typeof timeout !== "number") return false;
|
|
54119
|
+
return timeout > 0 && timeout <= LONGEST_TIMER_MS && (label2 === void 0 || label2 === waitName(timeout / 1e3));
|
|
54120
|
+
}
|
|
53770
54121
|
var MaestroParseError = class extends Error {
|
|
53771
54122
|
name = "MaestroParseError";
|
|
53772
54123
|
};
|
|
@@ -53956,6 +54307,9 @@ function parseStep(raw, position) {
|
|
|
53956
54307
|
expectKeys(arg, ["link"], `openLink at ${where}`);
|
|
53957
54308
|
return { kind: "openLink", link: expectString(arg.link, `openLink.link at ${where}`) };
|
|
53958
54309
|
}
|
|
54310
|
+
case "extendedWaitUntil":
|
|
54311
|
+
if (isWaitForm(arg)) return { kind: "wait", seconds: arg.timeout / 1e3 };
|
|
54312
|
+
throw new MaestroParseError(`Unsupported command "extendedWaitUntil" at ${where}`);
|
|
53959
54313
|
case "takeScreenshot": {
|
|
53960
54314
|
if (typeof arg === "string") return { kind: "takeScreenshot", path: arg };
|
|
53961
54315
|
if (!isRecord(arg)) throw new MaestroParseError(`takeScreenshot needs a path at ${where}`);
|
|
@@ -54005,7 +54359,8 @@ function fromFlow(flow) {
|
|
|
54005
54359
|
commands: flow.steps.map((step) => ({ kind: "step", step })),
|
|
54006
54360
|
maestroOnly: [],
|
|
54007
54361
|
variables: flowVariables(flow),
|
|
54008
|
-
flow
|
|
54362
|
+
flow,
|
|
54363
|
+
ownFlow: flow
|
|
54009
54364
|
};
|
|
54010
54365
|
}
|
|
54011
54366
|
function parseBlockFlow(yamlText) {
|
|
@@ -54104,6 +54459,10 @@ function stepToYaml(step) {
|
|
|
54104
54459
|
return { assertNotVisible: selectorToYaml(step.selector) };
|
|
54105
54460
|
case "openLink":
|
|
54106
54461
|
return { openLink: quoted(step.link) };
|
|
54462
|
+
case "wait":
|
|
54463
|
+
return {
|
|
54464
|
+
extendedWaitUntil: { visible: quoted(NEVER_ON_SCREEN), timeout: step.seconds * 1e3, optional: true, label: quoted(waitName(step.seconds)) }
|
|
54465
|
+
};
|
|
54107
54466
|
case "takeScreenshot":
|
|
54108
54467
|
return step.optional === void 0 ? { takeScreenshot: { path: step.path } } : { takeScreenshot: { path: step.path, optional: step.optional } };
|
|
54109
54468
|
case "startRecording":
|
|
@@ -54281,7 +54640,7 @@ function chooseSelector(node, allNodes, point, viewport) {
|
|
|
54281
54640
|
}
|
|
54282
54641
|
|
|
54283
54642
|
// ../../packages/core/src/project/store.ts
|
|
54284
|
-
import { access, cp, mkdir as
|
|
54643
|
+
import { access, cp, mkdir as mkdir3, readdir as readdir3, readFile as readFile2, rename, rm as rm3, writeFile as writeFile2 } from "node:fs/promises";
|
|
54285
54644
|
import { basename, dirname as dirname3, isAbsolute, join as join4, relative, resolve, sep } from "node:path";
|
|
54286
54645
|
|
|
54287
54646
|
// ../../packages/core/src/recorder/recorder.ts
|
|
@@ -54484,6 +54843,9 @@ var Recorder = class {
|
|
|
54484
54843
|
recordOpenLink(url, screenshotPath) {
|
|
54485
54844
|
return this.recordSimple({ kind: "openLink", url }, screenshotPath);
|
|
54486
54845
|
}
|
|
54846
|
+
recordWait(seconds, screenshotPath) {
|
|
54847
|
+
return this.recordSimple({ kind: "wait", seconds }, screenshotPath);
|
|
54848
|
+
}
|
|
54487
54849
|
recordHideKeyboard(screenshotPath) {
|
|
54488
54850
|
return this.recordSimple({ kind: "hideKeyboard" }, screenshotPath);
|
|
54489
54851
|
}
|
|
@@ -54549,6 +54911,7 @@ var Recorder = class {
|
|
|
54549
54911
|
expectations: this.expectations.map((expectation) => structuredClone(expectation)),
|
|
54550
54912
|
...meta.editedAt ? { editedAt: meta.editedAt } : {},
|
|
54551
54913
|
...meta.muted ? { muted: true } : {},
|
|
54914
|
+
...meta.reference ? { reference: meta.reference } : {},
|
|
54552
54915
|
...this.evidenceCounter > 0 ? { lastEvidenceNumber: this.evidenceCounter } : {}
|
|
54553
54916
|
};
|
|
54554
54917
|
}
|
|
@@ -54604,6 +54967,8 @@ function stepToMaestro(step, scrollUntilVisible, blockFlow) {
|
|
|
54604
54967
|
return { kind: "launchApp" };
|
|
54605
54968
|
case "openLink":
|
|
54606
54969
|
return { kind: "openLink", link: step.url };
|
|
54970
|
+
case "wait":
|
|
54971
|
+
return { kind: "wait", seconds: step.seconds };
|
|
54607
54972
|
case "hideKeyboard":
|
|
54608
54973
|
return { kind: "hideKeyboard" };
|
|
54609
54974
|
case "scroll":
|
|
@@ -54646,6 +55011,7 @@ function agentDeviceReplays(step) {
|
|
|
54646
55011
|
case "pressKey":
|
|
54647
55012
|
return AGENT_DEVICE_KEYS.has(step.key);
|
|
54648
55013
|
case "setOrientation":
|
|
55014
|
+
case "wait":
|
|
54649
55015
|
return false;
|
|
54650
55016
|
case "longPressOn":
|
|
54651
55017
|
case "doubleTapOn":
|
|
@@ -54669,6 +55035,8 @@ function directAction(step) {
|
|
|
54669
55035
|
} else if ((step.kind === "longPressOn" || step.kind === "doubleTapOn") && step.selector.kind === "point") {
|
|
54670
55036
|
const point = { xPercent: step.selector.xPercent, yPercent: step.selector.yPercent };
|
|
54671
55037
|
return { kind: step.kind === "longPressOn" ? "longPress" : "doubleTap", point };
|
|
55038
|
+
} else if (step.kind === "wait") {
|
|
55039
|
+
return { kind: "wait", seconds: step.seconds };
|
|
54672
55040
|
}
|
|
54673
55041
|
throw new Error(`The studio cannot replay ${JSON.stringify(step)} on the device`);
|
|
54674
55042
|
}
|
|
@@ -54702,18 +55070,46 @@ async function exists(path) {
|
|
|
54702
55070
|
return false;
|
|
54703
55071
|
}
|
|
54704
55072
|
}
|
|
54705
|
-
|
|
54706
|
-
|
|
54707
|
-
|
|
54708
|
-
|
|
55073
|
+
var IN_USE = /* @__PURE__ */ new Set(["EPERM", "EACCES", "EBUSY"]);
|
|
55074
|
+
var RENAME_TRIES = 10;
|
|
55075
|
+
async function renameWhenFree(from, to, platform = process.platform, move = rename) {
|
|
55076
|
+
for (let attempt = 1; ; attempt++) {
|
|
55077
|
+
try {
|
|
55078
|
+
return await move(from, to);
|
|
55079
|
+
} catch (error) {
|
|
55080
|
+
const code = error.code;
|
|
55081
|
+
if (platform !== "win32" || !code || !IN_USE.has(code) || attempt >= RENAME_TRIES) throw error;
|
|
55082
|
+
await new Promise((wake) => setTimeout(wake, Math.min(20 * 2 ** (attempt - 1), 400)));
|
|
55083
|
+
}
|
|
55084
|
+
}
|
|
54709
55085
|
}
|
|
54710
|
-
|
|
55086
|
+
var writing = /* @__PURE__ */ new Map();
|
|
55087
|
+
async function writeFileAtomic(path, content, move = rename) {
|
|
55088
|
+
const write = async () => {
|
|
55089
|
+
const temp = `${path}.${process.pid}.tmp`;
|
|
55090
|
+
await writeFile2(temp, content, "utf8");
|
|
55091
|
+
try {
|
|
55092
|
+
await renameWhenFree(temp, path, process.platform, move);
|
|
55093
|
+
} catch (error) {
|
|
55094
|
+
await rm3(temp, { force: true }).catch(() => void 0);
|
|
55095
|
+
throw error;
|
|
55096
|
+
}
|
|
55097
|
+
};
|
|
55098
|
+
const current = (writing.get(path) ?? Promise.resolve()).then(write, write);
|
|
55099
|
+
writing.set(path, current);
|
|
55100
|
+
try {
|
|
55101
|
+
await current;
|
|
55102
|
+
} finally {
|
|
55103
|
+
if (writing.get(path) === current) writing.delete(path);
|
|
55104
|
+
}
|
|
55105
|
+
}
|
|
55106
|
+
function slugify(name, fallback = "case") {
|
|
54711
55107
|
const slug = name.normalize("NFKD").replace(/[̀-ͯ]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 48).replace(/-+$/g, "");
|
|
54712
|
-
return slug ||
|
|
55108
|
+
return slug || fallback;
|
|
54713
55109
|
}
|
|
54714
55110
|
function timestampSuffix(date) {
|
|
54715
|
-
const
|
|
54716
|
-
return `${date.getFullYear()}${
|
|
55111
|
+
const pad2 = (value) => String(value).padStart(2, "0");
|
|
55112
|
+
return `${date.getFullYear()}${pad2(date.getMonth() + 1)}${pad2(date.getDate())}-${pad2(date.getHours())}${pad2(date.getMinutes())}`;
|
|
54717
55113
|
}
|
|
54718
55114
|
function makeCaseId(name, date) {
|
|
54719
55115
|
return `${slugify(name)}-${timestampSuffix(date)}`;
|
|
@@ -54789,16 +55185,16 @@ var ProjectStore = class _ProjectStore {
|
|
|
54789
55185
|
* project.json points elsewhere. Folders outside the project are not created.
|
|
54790
55186
|
*/
|
|
54791
55187
|
async ensureLayout() {
|
|
54792
|
-
await
|
|
55188
|
+
await mkdir3(this.dir, { recursive: true });
|
|
54793
55189
|
if (!await exists(this.path("project.json"))) {
|
|
54794
55190
|
await this.writeSettings(DEFAULT_SETTINGS);
|
|
54795
55191
|
if (!await exists(this.path(".gitignore"))) await writeFile2(this.path(".gitignore"), PROJECT_GITIGNORE, "utf8");
|
|
54796
55192
|
}
|
|
54797
55193
|
const settings = await this.readSettings();
|
|
54798
55194
|
this.paths = resolveProjectPaths(this.dir, settings.paths);
|
|
54799
|
-
for (const name of DIRS) await
|
|
55195
|
+
for (const name of DIRS) await mkdir3(join4(this.dir, name), { recursive: true });
|
|
54800
55196
|
for (const folder of [...this.paths.blockDirs, this.paths.flowsDir]) {
|
|
54801
|
-
if (isInside(folder, this.dir)) await
|
|
55197
|
+
if (isInside(folder, this.dir)) await mkdir3(folder, { recursive: true });
|
|
54802
55198
|
}
|
|
54803
55199
|
}
|
|
54804
55200
|
/** The block folders and the flows folder, absolute. */
|
|
@@ -54820,6 +55216,11 @@ var ProjectStore = class _ProjectStore {
|
|
|
54820
55216
|
runFlowFile(blockFile) {
|
|
54821
55217
|
return posixRelative(this.paths.flowsDir, resolve(this.dir, blockFile));
|
|
54822
55218
|
}
|
|
55219
|
+
/** How a block refers to another block: from its own folder, or from where a new block goes. */
|
|
55220
|
+
blockFlowPaths(file) {
|
|
55221
|
+
const folder = file === void 0 ? this.paths.blockDirs[0] ?? this.dir : dirname3(this.blockPath(file));
|
|
55222
|
+
return { runFlowFile: (blockFile) => posixRelative(folder, resolve(this.dir, blockFile)) };
|
|
55223
|
+
}
|
|
54823
55224
|
/** The flows folder as seen from `absoluteDir`, forward slashes, e.g. "../../flows" from runs/<runId>/. */
|
|
54824
55225
|
flowsDirFrom(absoluteDir) {
|
|
54825
55226
|
return posixRelative(absoluteDir, this.paths.flowsDir) || ".";
|
|
@@ -54891,7 +55292,7 @@ var ProjectStore = class _ProjectStore {
|
|
|
54891
55292
|
const blocks = [];
|
|
54892
55293
|
for (const dir of this.paths.blockDirs) {
|
|
54893
55294
|
if (!await exists(dir)) continue;
|
|
54894
|
-
const files = (await
|
|
55295
|
+
const files = (await readdir3(dir)).filter((file) => /\.ya?ml$/i.test(file)).sort();
|
|
54895
55296
|
for (const file of files) {
|
|
54896
55297
|
const absolute = join4(dir, file);
|
|
54897
55298
|
const projectPath = this.relative(absolute);
|
|
@@ -54916,11 +55317,81 @@ var ProjectStore = class _ProjectStore {
|
|
|
54916
55317
|
}
|
|
54917
55318
|
return blocks;
|
|
54918
55319
|
}
|
|
55320
|
+
/**
|
|
55321
|
+
* Writes a block over its own file, or with no `file` to a new one named
|
|
55322
|
+
* after it in the first block folder. Returns the block's project path.
|
|
55323
|
+
*/
|
|
55324
|
+
async writeBlock(flow, file) {
|
|
55325
|
+
const path = file === void 0 ? await this.newBlockPath(flow.name ?? "") : this.blockFilePath(file);
|
|
55326
|
+
await mkdir3(dirname3(path), { recursive: true });
|
|
55327
|
+
await writeFileAtomic(path, toMaestroYaml(flow));
|
|
55328
|
+
return this.relative(path);
|
|
55329
|
+
}
|
|
55330
|
+
async deleteBlock(file) {
|
|
55331
|
+
await rm3(this.blockFilePath(file));
|
|
55332
|
+
}
|
|
55333
|
+
/** blockPath for changing a block: unlike reading, it refuses every other file in the project, such as .env. */
|
|
55334
|
+
blockFilePath(file) {
|
|
55335
|
+
const path = this.blockPath(file);
|
|
55336
|
+
if (!/\.ya?ml$/i.test(path) || !this.paths.blockDirs.some((dir) => isInside(path, dir))) throw new Error(`Not a block file: ${file}`);
|
|
55337
|
+
return path;
|
|
55338
|
+
}
|
|
55339
|
+
/**
|
|
55340
|
+
* Whether `steps`, as the block `file`, lead back to that file, directly or
|
|
55341
|
+
* through the flows they run. A flow that cannot be read leads nowhere, and
|
|
55342
|
+
* listBlocks reports it on its own.
|
|
55343
|
+
*/
|
|
55344
|
+
async reachesBlock(file, steps) {
|
|
55345
|
+
const target = this.blockPath(file);
|
|
55346
|
+
const seen = /* @__PURE__ */ new Set();
|
|
55347
|
+
const follow = async (from, commands) => {
|
|
55348
|
+
for (const step of commands) {
|
|
55349
|
+
if (step.kind !== "runFlow" || step.file.includes("${")) continue;
|
|
55350
|
+
const next = resolve(dirname3(from), step.file);
|
|
55351
|
+
if (next === target) return true;
|
|
55352
|
+
if (seen.has(next)) continue;
|
|
55353
|
+
seen.add(next);
|
|
55354
|
+
if (await follow(next, await readSteps(next))) return true;
|
|
55355
|
+
}
|
|
55356
|
+
return false;
|
|
55357
|
+
};
|
|
55358
|
+
return follow(target, steps);
|
|
55359
|
+
}
|
|
55360
|
+
/** Where a new block with this name would go, as a project path. */
|
|
55361
|
+
async newBlockFile(name) {
|
|
55362
|
+
return this.relative(await this.newBlockPath(name));
|
|
55363
|
+
}
|
|
55364
|
+
/** "Log in" -> blocks/log-in.yaml, then log-in-2.yaml. A file a case or a block runs is never reused. */
|
|
55365
|
+
async newBlockPath(name) {
|
|
55366
|
+
const [dir] = this.paths.blockDirs;
|
|
55367
|
+
if (!dir) throw new Error('project.json lists no block folders in "paths.blocks".');
|
|
55368
|
+
const slug = slugify(name, "block");
|
|
55369
|
+
for (let number = 1; ; number++) {
|
|
55370
|
+
const base = join4(dir, number === 1 ? slug : `${slug}-${number}`);
|
|
55371
|
+
if (!await exists(`${base}.yaml`) && !await exists(`${base}.yml`)) return `${base}.yaml`;
|
|
55372
|
+
}
|
|
55373
|
+
}
|
|
55374
|
+
/** The names of the saved cases and the other blocks that run a block. */
|
|
55375
|
+
async blockUsers(file) {
|
|
55376
|
+
const cases = [];
|
|
55377
|
+
for (const summary of await this.listCases()) {
|
|
55378
|
+
const { name, preconditionBlocks, steps } = await this.readCase(summary.id);
|
|
55379
|
+
if (preconditionBlocks.some((use) => use.file === file) || steps.some((step) => step.kind === "block" && step.file === file)) cases.push(name);
|
|
55380
|
+
}
|
|
55381
|
+
const target = this.blockPath(file);
|
|
55382
|
+
const blocks = [];
|
|
55383
|
+
for (const block of await this.listBlocks()) {
|
|
55384
|
+
if (block.file === file) continue;
|
|
55385
|
+
const path = this.blockPath(block.file);
|
|
55386
|
+
if ((await readSteps(path)).some((step) => step.kind === "runFlow" && resolve(dirname3(path), step.file) === target)) blocks.push(block.name);
|
|
55387
|
+
}
|
|
55388
|
+
return [...cases, ...blocks];
|
|
55389
|
+
}
|
|
54919
55390
|
async listCases() {
|
|
54920
55391
|
const dir = this.path("cases");
|
|
54921
55392
|
if (!await exists(dir)) return [];
|
|
54922
55393
|
const summaries = [];
|
|
54923
|
-
for (const file of (await
|
|
55394
|
+
for (const file of (await readdir3(dir)).filter((name) => name.endsWith(".json"))) {
|
|
54924
55395
|
try {
|
|
54925
55396
|
const testCase = JSON.parse(await readFile2(join4(dir, file), "utf8"));
|
|
54926
55397
|
summaries.push({
|
|
@@ -54948,7 +55419,7 @@ var ProjectStore = class _ProjectStore {
|
|
|
54948
55419
|
}
|
|
54949
55420
|
/** Writes cases/<id>.json only; callers regenerate the flow with writeFlow. */
|
|
54950
55421
|
async writeCase(testCase) {
|
|
54951
|
-
await
|
|
55422
|
+
await mkdir3(this.path("cases"), { recursive: true });
|
|
54952
55423
|
await writeFileAtomic(this.casePath(testCase.id), `${JSON.stringify(testCase, null, 2)}
|
|
54953
55424
|
`);
|
|
54954
55425
|
}
|
|
@@ -54979,7 +55450,7 @@ var ProjectStore = class _ProjectStore {
|
|
|
54979
55450
|
const dir = this.evidenceDir(caseId);
|
|
54980
55451
|
if (!await exists(dir)) return 0;
|
|
54981
55452
|
let max = 0;
|
|
54982
|
-
for (const name of await
|
|
55453
|
+
for (const name of await readdir3(dir)) {
|
|
54983
55454
|
const match = /^step-(\d+)\.png$/.exec(name);
|
|
54984
55455
|
if (match) max = Math.max(max, Number(match[1]));
|
|
54985
55456
|
}
|
|
@@ -55017,7 +55488,7 @@ var ProjectStore = class _ProjectStore {
|
|
|
55017
55488
|
const source = this.path(path);
|
|
55018
55489
|
if (!await exists(source)) return void 0;
|
|
55019
55490
|
const target = `evidence/${toCaseId}/${basename(path)}`;
|
|
55020
|
-
await
|
|
55491
|
+
await mkdir3(this.evidenceDir(toCaseId), { recursive: true });
|
|
55021
55492
|
await cp(source, this.path(target));
|
|
55022
55493
|
return target;
|
|
55023
55494
|
}
|
|
@@ -55040,7 +55511,7 @@ var ProjectStore = class _ProjectStore {
|
|
|
55040
55511
|
return this.path(`${SUITES_DIR}/${suiteId}.json`);
|
|
55041
55512
|
}
|
|
55042
55513
|
async saveSuite(suite) {
|
|
55043
|
-
await
|
|
55514
|
+
await mkdir3(this.path(SUITES_DIR), { recursive: true });
|
|
55044
55515
|
await writeFileAtomic(this.suiteFile(suite.suiteId), `${JSON.stringify(suite, null, 2)}
|
|
55045
55516
|
`);
|
|
55046
55517
|
}
|
|
@@ -55051,7 +55522,7 @@ var ProjectStore = class _ProjectStore {
|
|
|
55051
55522
|
async listSuites(limit = 20) {
|
|
55052
55523
|
const dir = this.path(SUITES_DIR);
|
|
55053
55524
|
if (!await exists(dir)) return [];
|
|
55054
|
-
const names = (await
|
|
55525
|
+
const names = (await readdir3(dir)).filter((name) => name.endsWith(".json")).sort().reverse().slice(0, limit);
|
|
55055
55526
|
const suites = [];
|
|
55056
55527
|
for (const name of names) {
|
|
55057
55528
|
try {
|
|
@@ -55098,7 +55569,7 @@ var ProjectStore = class _ProjectStore {
|
|
|
55098
55569
|
}
|
|
55099
55570
|
async saveRun(summary) {
|
|
55100
55571
|
const dir = this.runDir(summary.runId);
|
|
55101
|
-
await
|
|
55572
|
+
await mkdir3(dir, { recursive: true });
|
|
55102
55573
|
const file = join4(dir, "summary.json");
|
|
55103
55574
|
await writeFileAtomic(file, `${JSON.stringify(summary, null, 2)}
|
|
55104
55575
|
`);
|
|
@@ -55113,7 +55584,7 @@ var ProjectStore = class _ProjectStore {
|
|
|
55113
55584
|
if (!await exists(dir)) return [];
|
|
55114
55585
|
const pattern = runIdPattern(caseId);
|
|
55115
55586
|
const runs = [];
|
|
55116
|
-
for (const name of (await
|
|
55587
|
+
for (const name of (await readdir3(dir)).filter((entry) => pattern.test(entry))) {
|
|
55117
55588
|
try {
|
|
55118
55589
|
runs.push(await this.readRun(name));
|
|
55119
55590
|
} catch {
|
|
@@ -55123,20 +55594,20 @@ var ProjectStore = class _ProjectStore {
|
|
|
55123
55594
|
}
|
|
55124
55595
|
/** Removes the case file, its flow, its screenshots and every run of it. */
|
|
55125
55596
|
async deleteCase(caseId) {
|
|
55126
|
-
await
|
|
55127
|
-
await
|
|
55128
|
-
await
|
|
55597
|
+
await rm3(this.casePath(caseId), { force: true });
|
|
55598
|
+
await rm3(this.flowPath(caseId), { force: true });
|
|
55599
|
+
await rm3(this.evidenceDir(caseId), { recursive: true, force: true });
|
|
55129
55600
|
const runs = this.path("runs");
|
|
55130
55601
|
if (!await exists(runs)) return;
|
|
55131
55602
|
const pattern = runIdPattern(caseId);
|
|
55132
|
-
for (const name of (await
|
|
55133
|
-
await
|
|
55603
|
+
for (const name of (await readdir3(runs)).filter((entry) => pattern.test(entry))) {
|
|
55604
|
+
await rm3(join4(runs, name), { recursive: true, force: true });
|
|
55134
55605
|
}
|
|
55135
55606
|
}
|
|
55136
55607
|
/** Writes cases/<id>.json and regenerates flows/<id>.yaml from `flow`. */
|
|
55137
55608
|
async saveCase(testCase, flow) {
|
|
55138
55609
|
const casePath = this.casePath(testCase.id);
|
|
55139
|
-
await
|
|
55610
|
+
await mkdir3(this.path("cases"), { recursive: true });
|
|
55140
55611
|
await writeFileAtomic(casePath, `${JSON.stringify(testCase, null, 2)}
|
|
55141
55612
|
`);
|
|
55142
55613
|
const { flowPath, yaml } = await this.writeFlow(testCase.id, flow);
|
|
@@ -55146,11 +55617,15 @@ var ProjectStore = class _ProjectStore {
|
|
|
55146
55617
|
async writeFlow(caseId, flow) {
|
|
55147
55618
|
const flowPath = this.flowPath(caseId);
|
|
55148
55619
|
const yaml = toMaestroYaml(flow);
|
|
55149
|
-
await
|
|
55620
|
+
await mkdir3(this.paths.flowsDir, { recursive: true });
|
|
55150
55621
|
await writeFile2(flowPath, yaml, "utf8");
|
|
55151
55622
|
return { flowPath, yaml };
|
|
55152
55623
|
}
|
|
55153
55624
|
};
|
|
55625
|
+
async function readSteps(path) {
|
|
55626
|
+
const outline = await readFile2(path, "utf8").then(parseBlockFlow).catch(() => void 0);
|
|
55627
|
+
return outline?.commands.flatMap((command) => command.kind === "step" ? [command.step] : []) ?? [];
|
|
55628
|
+
}
|
|
55154
55629
|
var MAX_RUNFLOW_DEPTH = 8;
|
|
55155
55630
|
async function readBlockOutline(absolute, seen = []) {
|
|
55156
55631
|
const outline = parseBlockFlow(await readFile2(absolute, "utf8"));
|
|
@@ -55174,7 +55649,8 @@ async function readBlockOutline(absolute, seen = []) {
|
|
|
55174
55649
|
try {
|
|
55175
55650
|
nested = await readBlockOutline(target, chain);
|
|
55176
55651
|
} catch (error) {
|
|
55177
|
-
|
|
55652
|
+
const reason = (error instanceof Error ? error.message : String(error)).split("\n")[0].replace(/:$/, "");
|
|
55653
|
+
throw new Error(`It runs ${file}, which cannot be read: ${reason}`);
|
|
55178
55654
|
}
|
|
55179
55655
|
for (const name of nested.maestroOnly) nestedOnly.add(name);
|
|
55180
55656
|
}
|
|
@@ -55357,8 +55833,8 @@ function relocateFlow(flow, flowsDirFromTarget) {
|
|
|
55357
55833
|
|
|
55358
55834
|
// ../../packages/core/src/replay/maestro-run.ts
|
|
55359
55835
|
import { spawn as spawn2 } from "node:child_process";
|
|
55360
|
-
import { access as access2, mkdir as
|
|
55361
|
-
import { join as join5 } from "node:path";
|
|
55836
|
+
import { access as access2, mkdir as mkdir4, readdir as readdir4, readFile as readFile3, writeFile as writeFile3 } from "node:fs/promises";
|
|
55837
|
+
import { join as join5, win32 as win324 } from "node:path";
|
|
55362
55838
|
|
|
55363
55839
|
// ../../packages/core/src/replay/run-result.ts
|
|
55364
55840
|
var BOOKKEEPING = /* @__PURE__ */ new Set(["defineVariablesCommand", "applyConfigurationCommand"]);
|
|
@@ -55534,7 +56010,7 @@ function deviceProblemField(passed, diagnostics, steps, appId) {
|
|
|
55534
56010
|
}
|
|
55535
56011
|
|
|
55536
56012
|
// ../../packages/core/src/replay/windows-command.ts
|
|
55537
|
-
import { win32 as
|
|
56013
|
+
import { win32 as win323 } from "node:path";
|
|
55538
56014
|
var CMD_META = /([()\][%!^"`<>&|;, *?])/g;
|
|
55539
56015
|
function quoteWindowsArgument(arg) {
|
|
55540
56016
|
let quoted2 = '"';
|
|
@@ -55557,7 +56033,7 @@ function escapeForCmd(text, parses) {
|
|
|
55557
56033
|
function spawnCommand(file, args, platform = process.platform, comspec = process.env.ComSpec) {
|
|
55558
56034
|
if (platform !== "win32" || !/\.(bat|cmd)$/i.test(file)) return { command: file, args: [...args] };
|
|
55559
56035
|
if (args.some((arg) => /[\r\n]/.test(arg))) throw new Error("An argument for a .bat or .cmd file cannot contain a line break.");
|
|
55560
|
-
const line = [`"${
|
|
56036
|
+
const line = [`"${win323.normalize(file)}"`, ...args.map((arg) => escapeForCmd(quoteWindowsArgument(arg), 2))].join(" ");
|
|
55561
56037
|
return { command: comspec || "cmd.exe", args: ["/d", "/s", "/v:off", "/c", `"${line}"`], windowsVerbatimArguments: true };
|
|
55562
56038
|
}
|
|
55563
56039
|
|
|
@@ -55574,18 +56050,24 @@ function stopRun(child) {
|
|
|
55574
56050
|
return setTimeout(() => child.kill("SIGKILL"), KILL_GRACE_MS);
|
|
55575
56051
|
}
|
|
55576
56052
|
function maestroEnv(androidHome2, env = process.env, platform = process.platform) {
|
|
55577
|
-
const
|
|
56053
|
+
const javaHome = toolsJavaHome(env, platform);
|
|
56054
|
+
const result = { ...env, ...androidHome2 ? { ANDROID_HOME: androidHome2 } : {}, ...javaHome ? { JAVA_HOME: javaHome } : {} };
|
|
55578
56055
|
if (platform === "win32") {
|
|
55579
|
-
result.MAESTRO_OPTS = [env.MAESTRO_OPTS, "-Dstdout.encoding=UTF-8", "-Dstderr.encoding=UTF-8"].filter(Boolean).join(" ");
|
|
56056
|
+
result.MAESTRO_OPTS = [env.MAESTRO_OPTS, "-Dfile.encoding=UTF-8", "-Dstdout.encoding=UTF-8", "-Dstderr.encoding=UTF-8"].filter(Boolean).join(" ");
|
|
55580
56057
|
}
|
|
55581
56058
|
return result;
|
|
55582
56059
|
}
|
|
56060
|
+
function pathForMaestro(path, cwd, platform = process.platform) {
|
|
56061
|
+
if (platform !== "win32" || isPlainAscii(path)) return path;
|
|
56062
|
+
const inside = win324.relative(cwd, path);
|
|
56063
|
+
return isPlainAscii(inside) && !inside.startsWith("..") && !win324.isAbsolute(inside) ? inside : path;
|
|
56064
|
+
}
|
|
55583
56065
|
async function runMaestro(options) {
|
|
55584
|
-
await
|
|
55585
|
-
const args = ["--device", options.deviceId, "test", "--no-ansi", "--debug-output", options.debugDir, "--flatten-debug-output"];
|
|
56066
|
+
await mkdir4(options.debugDir, { recursive: true });
|
|
56067
|
+
const args = ["--device", options.deviceId, "test", "--no-ansi", "--debug-output", pathForMaestro(options.debugDir, options.cwd), "--flatten-debug-output"];
|
|
55586
56068
|
if (options.keepDriver) args.push("--no-reinstall-driver");
|
|
55587
56069
|
for (const [key, value] of Object.entries(options.env)) args.push("-e", `${key}=${value}`);
|
|
55588
|
-
args.push(options.flowPath);
|
|
56070
|
+
args.push(pathForMaestro(options.flowPath, options.cwd));
|
|
55589
56071
|
const outputFile = join5(options.debugDir, "output.txt");
|
|
55590
56072
|
const chunks = [];
|
|
55591
56073
|
const started2 = Date.now();
|
|
@@ -55633,7 +56115,7 @@ async function exists2(path) {
|
|
|
55633
56115
|
}
|
|
55634
56116
|
}
|
|
55635
56117
|
async function readMaestroCommands(debugDir) {
|
|
55636
|
-
const entries = await
|
|
56118
|
+
const entries = await readdir4(debugDir, { withFileTypes: true }).catch(() => []);
|
|
55637
56119
|
const dirs = entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name).sort();
|
|
55638
56120
|
for (const name of dirs) {
|
|
55639
56121
|
const file = join5(debugDir, name, "commands.json");
|
|
@@ -55652,7 +56134,7 @@ function redactStrings(value, env) {
|
|
|
55652
56134
|
return value;
|
|
55653
56135
|
}
|
|
55654
56136
|
async function redactRunFiles(dir, env) {
|
|
55655
|
-
const entries = await
|
|
56137
|
+
const entries = await readdir4(dir, { withFileTypes: true, recursive: true }).catch(() => []);
|
|
55656
56138
|
for (const entry of entries) {
|
|
55657
56139
|
if (!entry.isFile() || !TEXT_FILE.test(entry.name)) continue;
|
|
55658
56140
|
const file = join5(entry.parentPath, entry.name);
|
|
@@ -55925,18 +56407,18 @@ function caseHealth(runs, editedAt, limit = 10) {
|
|
|
55925
56407
|
// ../studio/src/server/index.ts
|
|
55926
56408
|
var import_static = __toESM(require_static(), 1);
|
|
55927
56409
|
var import_fastify = __toESM(require_fastify(), 1);
|
|
55928
|
-
import { existsSync as
|
|
55929
|
-
import { rm as
|
|
55930
|
-
import { resolve as
|
|
56410
|
+
import { existsSync as existsSync6 } from "node:fs";
|
|
56411
|
+
import { rm as rm11 } from "node:fs/promises";
|
|
56412
|
+
import { resolve as resolve4 } from "node:path";
|
|
55931
56413
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
55932
56414
|
|
|
55933
56415
|
// ../studio/src/server/phone-capture.ts
|
|
55934
|
-
import { appendFile, copyFile, mkdir as
|
|
56416
|
+
import { appendFile, copyFile as copyFile2, mkdir as mkdir6 } from "node:fs/promises";
|
|
55935
56417
|
import { dirname as dirname5 } from "node:path";
|
|
55936
56418
|
|
|
55937
56419
|
// ../studio/src/server/common.ts
|
|
55938
56420
|
import { createReadStream } from "node:fs";
|
|
55939
|
-
import { access as access3, mkdir as
|
|
56421
|
+
import { access as access3, copyFile, mkdir as mkdir5, readFile as readFile4, rename as rename2, rm as rm4, writeFile as writeFile4 } from "node:fs/promises";
|
|
55940
56422
|
import { dirname as dirname4 } from "node:path";
|
|
55941
56423
|
|
|
55942
56424
|
// ../studio/src/server/state.ts
|
|
@@ -55955,6 +56437,9 @@ function enqueue(state, task) {
|
|
|
55955
56437
|
function scratchScreenshot(state) {
|
|
55956
56438
|
return join6(state.scratchDir, "screen.png");
|
|
55957
56439
|
}
|
|
56440
|
+
function capturingScreenshot(state) {
|
|
56441
|
+
return join6(state.scratchDir, "capturing.png");
|
|
56442
|
+
}
|
|
55958
56443
|
function toLiteNode(node) {
|
|
55959
56444
|
const lite = {};
|
|
55960
56445
|
if (node.ref) lite.ref = node.ref;
|
|
@@ -55974,7 +56459,10 @@ function toScreenState(latest) {
|
|
|
55974
56459
|
height: latest.viewport.height,
|
|
55975
56460
|
imageUrl: `/api/screen/image?ts=${Date.parse(latest.takenAt) || Date.now()}`,
|
|
55976
56461
|
takenAt: latest.takenAt,
|
|
55977
|
-
nodes: latest.nodes.filter((node) => node.visibleToUser !== false && node.interactionBlocked === void 0 && node.rect).map(toLiteNode)
|
|
56462
|
+
nodes: latest.nodes.filter((node) => node.visibleToUser !== false && node.interactionBlocked === void 0 && node.rect).map(toLiteNode),
|
|
56463
|
+
...latest.hidden ? { hidden: true } : {},
|
|
56464
|
+
...latest.secure ? { secure: true } : {},
|
|
56465
|
+
...latest.elsewhere ? { elsewhere: latest.elsewhere } : {}
|
|
55978
56466
|
};
|
|
55979
56467
|
}
|
|
55980
56468
|
function evidenceUrl(projectRelative) {
|
|
@@ -55991,6 +56479,7 @@ function currentCaseView(state) {
|
|
|
55991
56479
|
if (!state.recorder?.active) return null;
|
|
55992
56480
|
const view = toCaseView(state.recorder.toCase());
|
|
55993
56481
|
const editing = state.editing;
|
|
56482
|
+
if (state.recordingBlock) return { ...view, forBlock: true };
|
|
55994
56483
|
return editing ? { ...view, editing: { mode: editing.mode, keptSteps: editing.keptSteps, tailSteps: editing.tail.length } } : view;
|
|
55995
56484
|
}
|
|
55996
56485
|
function suiteRunning(state) {
|
|
@@ -56047,6 +56536,29 @@ function requireSession(state) {
|
|
|
56047
56536
|
throw new HttpError(409, "NO_SESSION", "No device connected. Pick a device, enter the app id and click Connect first.");
|
|
56048
56537
|
}
|
|
56049
56538
|
}
|
|
56539
|
+
function readPicks(body) {
|
|
56540
|
+
const picks = (body ?? {}).picks;
|
|
56541
|
+
if (picks === void 0) return [];
|
|
56542
|
+
if (!Array.isArray(picks) || !picks.every((pick) => Number.isInteger(pick) && pick >= 0)) {
|
|
56543
|
+
throw new HttpError(400, "BAD_PICK", 'Expected "picks" to be a list of candidate ids.');
|
|
56544
|
+
}
|
|
56545
|
+
return [...new Set(picks)];
|
|
56546
|
+
}
|
|
56547
|
+
function pickedCandidates(state, picks) {
|
|
56548
|
+
const screen = state.offered ?? state.latest;
|
|
56549
|
+
const candidates = screen ? expectationCandidates(screen.nodes, screen.viewport) : [];
|
|
56550
|
+
return picks.map((pick) => {
|
|
56551
|
+
const candidate = candidates[pick];
|
|
56552
|
+
if (!candidate) throw new HttpError(400, "BAD_PICK", `There is no candidate ${pick} on the current screen.`);
|
|
56553
|
+
return candidate;
|
|
56554
|
+
});
|
|
56555
|
+
}
|
|
56556
|
+
function requireNoRecording(state) {
|
|
56557
|
+
if (state.recorder?.active) throw new HttpError(409, "CASE_ACTIVE", "Finish or discard the recording first.");
|
|
56558
|
+
}
|
|
56559
|
+
function requireRecording(state, recorder) {
|
|
56560
|
+
if (state.recorder !== recorder) throw new HttpError(409, "NO_CASE", "That recording has already ended.");
|
|
56561
|
+
}
|
|
56050
56562
|
function requireCase(state) {
|
|
56051
56563
|
if (!state.recorder?.active) throw new HttpError(409, "NO_CASE", "No case is being recorded. Start a case first.");
|
|
56052
56564
|
return state.recorder;
|
|
@@ -56055,8 +56567,43 @@ async function projectEnv(state) {
|
|
|
56055
56567
|
const settings = await state.project.readSettings();
|
|
56056
56568
|
return { ...settings.env ?? {}, ...await state.project.readEnv() };
|
|
56057
56569
|
}
|
|
56570
|
+
async function phoneLocked(state) {
|
|
56571
|
+
const { deviceId } = state.session;
|
|
56572
|
+
if (!deviceId) return false;
|
|
56573
|
+
const locked = await state.phoneTools.locked(deviceId).catch(() => void 0);
|
|
56574
|
+
if (locked === void 0) return state.lockAnswered === deviceId;
|
|
56575
|
+
state.lockAnswered = deviceId;
|
|
56576
|
+
return locked;
|
|
56577
|
+
}
|
|
56578
|
+
async function movePicture(from, path) {
|
|
56579
|
+
await mkdir5(dirname4(path), { recursive: true });
|
|
56580
|
+
try {
|
|
56581
|
+
await rename2(from, path);
|
|
56582
|
+
} catch {
|
|
56583
|
+
await copyFile(from, path);
|
|
56584
|
+
await rm4(from, { force: true });
|
|
56585
|
+
}
|
|
56586
|
+
}
|
|
56058
56587
|
async function captureInto(state, path) {
|
|
56059
|
-
|
|
56588
|
+
const capture = await state.session.capture(capturingScreenshot(state));
|
|
56589
|
+
let locked;
|
|
56590
|
+
try {
|
|
56591
|
+
locked = await phoneLocked(state);
|
|
56592
|
+
if (locked) await writeFile4(capture.screenshotPath, blackPicture(capture.viewport));
|
|
56593
|
+
await movePicture(capture.screenshotPath, path);
|
|
56594
|
+
} catch (error) {
|
|
56595
|
+
await rm4(capture.screenshotPath, { force: true }).catch(() => void 0);
|
|
56596
|
+
throw error;
|
|
56597
|
+
}
|
|
56598
|
+
const saved = { ...capture, screenshotPath: path };
|
|
56599
|
+
if (locked) {
|
|
56600
|
+
state.latest = { ...saved, nodes: [], keyboard: void 0, hidden: true };
|
|
56601
|
+
return state.latest;
|
|
56602
|
+
}
|
|
56603
|
+
const view = screenView(capture.nodes, await readFile4(path).catch(() => new Uint8Array()));
|
|
56604
|
+
const front = view === "hidden" ? void 0 : foregroundApp(capture);
|
|
56605
|
+
const elsewhere = front && front !== state.session.appId && front !== SYSTEM_UI ? { app: front, home: looksLikeHomeApp(front) } : void 0;
|
|
56606
|
+
state.latest = { ...saved, ...view === "hidden" ? { hidden: true } : {}, ...view === "secure" ? { secure: true } : {}, ...elsewhere ? { elsewhere } : {} };
|
|
56060
56607
|
return state.latest;
|
|
56061
56608
|
}
|
|
56062
56609
|
async function captureScratch(state) {
|
|
@@ -56126,6 +56673,8 @@ async function performDirect(state, action) {
|
|
|
56126
56673
|
const at = pointOnScreen(action.point, (await state.session.snapshot()).viewport);
|
|
56127
56674
|
return action.kind === "longPress" ? state.session.longPressAt(at) : state.session.doubleTapAt(at);
|
|
56128
56675
|
}
|
|
56676
|
+
case "wait":
|
|
56677
|
+
return new Promise((resolve7) => setTimeout(resolve7, action.seconds * 1e3));
|
|
56129
56678
|
}
|
|
56130
56679
|
}
|
|
56131
56680
|
async function replayOnDevice(state, flow, steps, env, scratchPath) {
|
|
@@ -56136,7 +56685,7 @@ async function replayOnDevice(state, flow, steps, env, scratchPath) {
|
|
|
56136
56685
|
}
|
|
56137
56686
|
const header = flow.name === void 0 ? { appId: flow.appId } : { appId: flow.appId, name: flow.name };
|
|
56138
56687
|
const relocated = relocateFlow({ ...header, steps: segment.steps }, state.project.flowsDirFrom(dirname4(scratchPath)));
|
|
56139
|
-
await
|
|
56688
|
+
await mkdir5(dirname4(scratchPath), { recursive: true });
|
|
56140
56689
|
await writeFile4(scratchPath, toMaestroYaml(relocated), "utf8");
|
|
56141
56690
|
await state.session.replayFlow(scratchPath, env);
|
|
56142
56691
|
}
|
|
@@ -56154,8 +56703,8 @@ var KEYBOARD_OPENS_WITHIN_MS = 1500;
|
|
|
56154
56703
|
var EVIDENCE_DELAY_MS = 900;
|
|
56155
56704
|
var EVIDENCE_LIMIT_MS = 4e3;
|
|
56156
56705
|
var MAX_SNAPSHOT_FAILURES = 25;
|
|
56706
|
+
var LOCK_CHECK_MS = 1e3;
|
|
56157
56707
|
var STALE_SNAPSHOT_MS = 1e3;
|
|
56158
|
-
var SYSTEM_UI = "com.android.systemui";
|
|
56159
56708
|
var TIMINGS_KEPT = 4e3;
|
|
56160
56709
|
function bump(counts, key) {
|
|
56161
56710
|
counts[key] = (counts[key] ?? 0) + 1;
|
|
@@ -56179,6 +56728,8 @@ var PhoneCapture = class {
|
|
|
56179
56728
|
stopped = false;
|
|
56180
56729
|
paused = false;
|
|
56181
56730
|
cameBack = false;
|
|
56731
|
+
locked = false;
|
|
56732
|
+
lockCheckedAt = 0;
|
|
56182
56733
|
revision = 0;
|
|
56183
56734
|
noticeCount = 0;
|
|
56184
56735
|
notice;
|
|
@@ -56245,7 +56796,7 @@ var PhoneCapture = class {
|
|
|
56245
56796
|
...this.state.phoneError ? { stoppedBy: this.state.phoneError } : {}
|
|
56246
56797
|
};
|
|
56247
56798
|
const path = this.state.project.path("diagnostics/phone-sessions.jsonl");
|
|
56248
|
-
await
|
|
56799
|
+
await mkdir6(dirname5(path), { recursive: true });
|
|
56249
56800
|
await appendFile(path, `${JSON.stringify(line)}
|
|
56250
56801
|
`, "utf8");
|
|
56251
56802
|
}
|
|
@@ -56273,7 +56824,7 @@ var PhoneCapture = class {
|
|
|
56273
56824
|
return;
|
|
56274
56825
|
}
|
|
56275
56826
|
}
|
|
56276
|
-
await new Promise((
|
|
56827
|
+
await new Promise((resolve7) => setTimeout(resolve7, SNAPSHOT_GAP_MS));
|
|
56277
56828
|
}
|
|
56278
56829
|
}
|
|
56279
56830
|
async see() {
|
|
@@ -56296,18 +56847,30 @@ var PhoneCapture = class {
|
|
|
56296
56847
|
this.viewport = viewport;
|
|
56297
56848
|
const screen = { nodes: snapshot.nodes, viewport, startedAt, endedAt, ...snapshot.keyboard ? { keyboard: snapshot.keyboard } : {} };
|
|
56298
56849
|
const app = foregroundApp(screen);
|
|
56299
|
-
this.trackApp(app);
|
|
56850
|
+
this.trackApp(app, await this.lockScreenUp());
|
|
56300
56851
|
if (!this.paused) this.noteField(screen);
|
|
56301
56852
|
this.history.push({ ...screen, ...app ? { app } : {}, outside: this.paused });
|
|
56302
56853
|
if (this.history.length > HISTORY) this.history.shift();
|
|
56303
56854
|
}
|
|
56304
|
-
/**
|
|
56305
|
-
|
|
56306
|
-
|
|
56855
|
+
/**
|
|
56856
|
+
* Android's word for it, at most once a second. A lock screen need not be
|
|
56857
|
+
* System UI on every maker's phone, and its PIN pad must never become steps.
|
|
56858
|
+
*/
|
|
56859
|
+
async lockScreenUp() {
|
|
56860
|
+
const now = Date.now();
|
|
56861
|
+
if (now - this.lockCheckedAt >= LOCK_CHECK_MS) {
|
|
56862
|
+
this.lockCheckedAt = now;
|
|
56863
|
+
this.locked = await phoneLocked(this.state);
|
|
56864
|
+
}
|
|
56865
|
+
return this.locked;
|
|
56866
|
+
}
|
|
56867
|
+
/** Home, the launcher, the notification shade or the lock screen pause recording until the app is back. */
|
|
56868
|
+
trackApp(app, locked) {
|
|
56869
|
+
if (app === this.appId && this.paused && !locked) {
|
|
56307
56870
|
this.paused = false;
|
|
56308
56871
|
this.cameBack = true;
|
|
56309
56872
|
this.revision += 1;
|
|
56310
|
-
} else if (app !== void 0 &&
|
|
56873
|
+
} else if (!this.paused && (locked || app !== void 0 && (app === this.input.homeApp || app === SYSTEM_UI))) {
|
|
56311
56874
|
this.paused = true;
|
|
56312
56875
|
this.counts.pauses += 1;
|
|
56313
56876
|
this.revision += 1;
|
|
@@ -56317,7 +56880,7 @@ var PhoneCapture = class {
|
|
|
56317
56880
|
const keyboardOpening = this.typing !== void 0 && gesture.downAt - this.focusedAt < KEYBOARD_OPENS_WITHIN_MS;
|
|
56318
56881
|
const until = Date.now() + (keyboardOpening ? OPENING_WAIT_MS : AT_TOUCH_WAIT_MS);
|
|
56319
56882
|
while (!this.history.some((screen) => screen.startedAt >= gesture.downAt) && Date.now() < until && !this.stopped) {
|
|
56320
|
-
await new Promise((
|
|
56883
|
+
await new Promise((resolve7) => setTimeout(resolve7, SNAPSHOT_GAP_MS));
|
|
56321
56884
|
}
|
|
56322
56885
|
const finished = this.history.filter((screen) => screen.endedAt <= gesture.downAt);
|
|
56323
56886
|
const usable = finished.filter((screen) => screen.app !== void 0);
|
|
@@ -56457,9 +57020,7 @@ var PhoneCapture = class {
|
|
|
56457
57020
|
if (typing.password) {
|
|
56458
57021
|
if (typing.secretKeys === 0) return;
|
|
56459
57022
|
typing.secretKeys = 0;
|
|
56460
|
-
const
|
|
56461
|
-
const key = keys.find((candidate) => /pass/i.test(candidate)) ?? "PASSWORD";
|
|
56462
|
-
const text2 = `\${${key}}`;
|
|
57023
|
+
const text2 = reference(passwordSetting(Object.keys(await projectEnv(this.state))));
|
|
56463
57024
|
this.record("type", text2, (recorder, screenshot) => recorder.recordInput(text2, screenshot), "password");
|
|
56464
57025
|
return;
|
|
56465
57026
|
}
|
|
@@ -56487,9 +57048,9 @@ var PhoneCapture = class {
|
|
|
56487
57048
|
const [first, ...others] = pending;
|
|
56488
57049
|
if (!first) return;
|
|
56489
57050
|
try {
|
|
56490
|
-
await
|
|
57051
|
+
await mkdir6(dirname5(first.path), { recursive: true });
|
|
56491
57052
|
await captureInto(this.state, first.path);
|
|
56492
|
-
for (const other of others) await
|
|
57053
|
+
for (const other of others) await copyFile2(first.path, other.path);
|
|
56493
57054
|
} catch (error) {
|
|
56494
57055
|
for (const item of pending) delete item.step.screenshot;
|
|
56495
57056
|
console.error(`[studio] no screenshot for a phone step: ${describeDeviceError(error).message}`);
|
|
@@ -56563,19 +57124,244 @@ function registerPhoneRoutes(app, state) {
|
|
|
56563
57124
|
}
|
|
56564
57125
|
|
|
56565
57126
|
// ../studio/src/server/diagnostics.ts
|
|
56566
|
-
import { readFile as
|
|
57127
|
+
import { readFile as readFile5, readdir as readdir6 } from "node:fs/promises";
|
|
56567
57128
|
import { homedir as homedir3, release, userInfo } from "node:os";
|
|
56568
|
-
import { join as
|
|
57129
|
+
import { join as join9 } from "node:path";
|
|
57130
|
+
|
|
57131
|
+
// ../studio/src/server/setup.ts
|
|
57132
|
+
import { execFile as execFile5, spawn as spawn3 } from "node:child_process";
|
|
57133
|
+
import { existsSync as existsSync4 } from "node:fs";
|
|
57134
|
+
import { delimiter, join as join8 } from "node:path";
|
|
57135
|
+
|
|
57136
|
+
// ../studio/src/shared/setup-steps.ts
|
|
57137
|
+
var MIN_JAVA = 17;
|
|
57138
|
+
function javaIsUsable(java) {
|
|
57139
|
+
return java.found && (java.major === void 0 || java.major >= MIN_JAVA);
|
|
57140
|
+
}
|
|
57141
|
+
function missingTools(info) {
|
|
57142
|
+
const missing = [];
|
|
57143
|
+
if (!info.adb.found) missing.push("adb");
|
|
57144
|
+
if (!javaIsUsable(info.java)) missing.push("java");
|
|
57145
|
+
if (!info.maestro.found) missing.push("maestro");
|
|
57146
|
+
return missing;
|
|
57147
|
+
}
|
|
57148
|
+
|
|
57149
|
+
// ../studio/src/server/tool-install.ts
|
|
57150
|
+
import { execFile as execFile4 } from "node:child_process";
|
|
57151
|
+
import { createHash } from "node:crypto";
|
|
57152
|
+
import { createWriteStream } from "node:fs";
|
|
57153
|
+
import { cp as cp2, mkdir as mkdir7, readdir as readdir5, rename as rename3, rm as rm5 } from "node:fs/promises";
|
|
57154
|
+
import { dirname as dirname6, join as join7 } from "node:path";
|
|
57155
|
+
import { Readable, Transform } from "node:stream";
|
|
57156
|
+
import { pipeline } from "node:stream/promises";
|
|
57157
|
+
import { setTimeout as sleep } from "node:timers/promises";
|
|
57158
|
+
var WINDOWS_X64 = [
|
|
57159
|
+
{
|
|
57160
|
+
id: "adb",
|
|
57161
|
+
name: "Android tools",
|
|
57162
|
+
url: "https://dl.google.com/android/repository/platform-tools_r37.0.1-win.zip",
|
|
57163
|
+
bytes: 8044989,
|
|
57164
|
+
sha256: "45f4d63113e895ebde0c90f194099a4676b6ac653bd28d54314a9e022bbc1a99",
|
|
57165
|
+
host: "dl.google.com",
|
|
57166
|
+
check: { file: "adb.exe", args: ["version"] }
|
|
57167
|
+
},
|
|
57168
|
+
{
|
|
57169
|
+
id: "java",
|
|
57170
|
+
name: "Java",
|
|
57171
|
+
url: "https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.21%2B9/OpenJDK17U-jre_x64_windows_hotspot_17.0.21_9.zip",
|
|
57172
|
+
bytes: 43860024,
|
|
57173
|
+
sha256: "fe4656bd78fb09812dc38c49da777f79cc33e7ee435a254be82f9e7f40d9d117",
|
|
57174
|
+
host: "github.com",
|
|
57175
|
+
check: { file: join7("bin", "java.exe"), args: ["-version"] }
|
|
57176
|
+
},
|
|
57177
|
+
{
|
|
57178
|
+
id: "maestro",
|
|
57179
|
+
name: "Maestro",
|
|
57180
|
+
url: "https://github.com/mobile-dev-inc/maestro/releases/download/cli-2.12.1/maestro.zip",
|
|
57181
|
+
bytes: 349832427,
|
|
57182
|
+
sha256: "54dbb0716425626604973cbb374c585ca30272375d9ce15177a1152765ddd9d7",
|
|
57183
|
+
host: "github.com",
|
|
57184
|
+
check: { file: join7("bin", "maestro.bat"), args: ["--version"] }
|
|
57185
|
+
}
|
|
57186
|
+
];
|
|
57187
|
+
var STALL_MS = 6e4;
|
|
57188
|
+
var CHECK_TIMEOUT_MS = 12e4;
|
|
57189
|
+
var UNZIP_TIMEOUT_MS = 3e5;
|
|
57190
|
+
var MOVE_ATTEMPTS = 6;
|
|
57191
|
+
var MOVE_RETRY_MS = 500;
|
|
57192
|
+
function unzipWithTar(zipFile, into) {
|
|
57193
|
+
const tar = join7(process.env.SystemRoot ?? "C:\\Windows", "System32", "tar.exe");
|
|
57194
|
+
const folder = dirname6(zipFile);
|
|
57195
|
+
return new Promise((resolvePromise, reject) => {
|
|
57196
|
+
const args = ["-xf", zipFile.slice(folder.length + 1), "-C", into.slice(folder.length + 1)];
|
|
57197
|
+
execFile4(tar, args, { cwd: folder, timeout: UNZIP_TIMEOUT_MS, windowsHide: true }, (error) => error ? reject(error) : resolvePromise());
|
|
57198
|
+
});
|
|
57199
|
+
}
|
|
57200
|
+
function startProgram(file, args, androidHome2) {
|
|
57201
|
+
const command = spawnCommand(file, args);
|
|
57202
|
+
return new Promise((resolvePromise, reject) => {
|
|
57203
|
+
execFile4(
|
|
57204
|
+
command.command,
|
|
57205
|
+
command.args,
|
|
57206
|
+
{ env: maestroEnv(androidHome2()), timeout: CHECK_TIMEOUT_MS, windowsHide: true, windowsVerbatimArguments: command.windowsVerbatimArguments ?? false },
|
|
57207
|
+
(error) => error ? reject(error) : resolvePromise()
|
|
57208
|
+
);
|
|
57209
|
+
});
|
|
57210
|
+
}
|
|
57211
|
+
function defaultInstallIo(androidHome2, env = process.env, platform = process.platform, arch = process.arch) {
|
|
57212
|
+
const tools = toolsTarget(env, platform);
|
|
57213
|
+
const shared = sharedToolsRoot(env, platform);
|
|
57214
|
+
return {
|
|
57215
|
+
downloads: tools && arch === "x64" ? WINDOWS_X64 : [],
|
|
57216
|
+
workDir: join7(tools ?? "", ".incoming"),
|
|
57217
|
+
destination: (id) => id === "adb" ? join7(androidHome2() ?? defaultSdkDir(), "platform-tools") : join7(tools ?? "", id),
|
|
57218
|
+
fetch: (...args) => fetch(...args),
|
|
57219
|
+
unzip: unzipWithTar,
|
|
57220
|
+
start: (file, args) => startProgram(file, args, androidHome2),
|
|
57221
|
+
prepare: async () => {
|
|
57222
|
+
if (!shared) return;
|
|
57223
|
+
await claimFolder(shared).catch((error) => {
|
|
57224
|
+
console.error(`[studio] Set up this computer: claiming ${shared}: ${error instanceof Error ? error.message : String(error)}`);
|
|
57225
|
+
throw new PlainError(`Ariadne will not run tools from ${shared}, because another account on this computer may be able to change them. Ask your IT team to check who owns that folder, because another account's tools may be in it, then press Try again.`);
|
|
57226
|
+
});
|
|
57227
|
+
}
|
|
57228
|
+
};
|
|
57229
|
+
}
|
|
57230
|
+
var PlainError = class extends Error {
|
|
57231
|
+
};
|
|
57232
|
+
function codeOf(error) {
|
|
57233
|
+
return error?.code;
|
|
57234
|
+
}
|
|
57235
|
+
function explain(download, error) {
|
|
57236
|
+
if (error instanceof PlainError) return error.message;
|
|
57237
|
+
const code = codeOf(error);
|
|
57238
|
+
if (code === "ENOSPC") return `There is not enough free disk space for ${download.name}. Free some space and press Try again.`;
|
|
57239
|
+
if (code === "EACCES" || code === "EPERM" || code === "EBUSY") {
|
|
57240
|
+
return `Ariadne could not save ${download.name} on this computer. Press Try again. If it keeps failing, an antivirus or a company rule may be blocking it.`;
|
|
57241
|
+
}
|
|
57242
|
+
return `Could not download ${download.name}. Check the internet connection and press Try again. On a company computer, ask your IT team to allow ${download.host}.`;
|
|
57243
|
+
}
|
|
57244
|
+
async function moveFolder(from, to) {
|
|
57245
|
+
for (let attempt = 1; attempt < MOVE_ATTEMPTS; attempt += 1) {
|
|
57246
|
+
try {
|
|
57247
|
+
await rename3(from, to);
|
|
57248
|
+
return;
|
|
57249
|
+
} catch (error) {
|
|
57250
|
+
if (codeOf(error) === "EXDEV") break;
|
|
57251
|
+
await sleep(MOVE_RETRY_MS);
|
|
57252
|
+
}
|
|
57253
|
+
}
|
|
57254
|
+
await cp2(from, to, { recursive: true });
|
|
57255
|
+
}
|
|
57256
|
+
var ToolInstaller = class {
|
|
57257
|
+
constructor(io) {
|
|
57258
|
+
this.io = io;
|
|
57259
|
+
}
|
|
57260
|
+
io;
|
|
57261
|
+
running = false;
|
|
57262
|
+
round = Promise.resolve();
|
|
57263
|
+
tools = [];
|
|
57264
|
+
status() {
|
|
57265
|
+
return {
|
|
57266
|
+
available: this.io.downloads.length > 0,
|
|
57267
|
+
running: this.running,
|
|
57268
|
+
offered: this.io.downloads.map(({ id, name, bytes }) => ({ id, name, bytes })),
|
|
57269
|
+
tools: this.tools.map((tool) => ({ ...tool }))
|
|
57270
|
+
};
|
|
57271
|
+
}
|
|
57272
|
+
/** Starts fetching these tools in the background; the guide follows with status(). */
|
|
57273
|
+
start(ids) {
|
|
57274
|
+
if (this.running) return;
|
|
57275
|
+
const chosen = this.io.downloads.filter((download) => ids.includes(download.id));
|
|
57276
|
+
if (chosen.length === 0) return;
|
|
57277
|
+
this.tools = chosen.map(({ id, name, bytes }) => ({ id, name, state: "waiting", received: 0, total: bytes }));
|
|
57278
|
+
this.running = true;
|
|
57279
|
+
this.round = this.installAll(chosen).finally(() => {
|
|
57280
|
+
this.running = false;
|
|
57281
|
+
});
|
|
57282
|
+
}
|
|
57283
|
+
/** Settles when every tool has had its turn. One that fails does not stop the next. */
|
|
57284
|
+
finished() {
|
|
57285
|
+
return this.round;
|
|
57286
|
+
}
|
|
57287
|
+
async installAll(chosen) {
|
|
57288
|
+
for (const download of chosen) {
|
|
57289
|
+
const progress = this.tools.find((tool) => tool.id === download.id);
|
|
57290
|
+
try {
|
|
57291
|
+
await this.install(download, progress);
|
|
57292
|
+
progress.state = "done";
|
|
57293
|
+
} catch (error) {
|
|
57294
|
+
progress.state = "failed";
|
|
57295
|
+
progress.error = explain(download, error);
|
|
57296
|
+
console.error(`[studio] Set up this computer: ${download.name} failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
57297
|
+
}
|
|
57298
|
+
}
|
|
57299
|
+
await rm5(this.io.workDir, { recursive: true, force: true, maxRetries: 3 }).catch(() => void 0);
|
|
57300
|
+
}
|
|
57301
|
+
async install(download, progress) {
|
|
57302
|
+
await this.io.prepare?.();
|
|
57303
|
+
const work = join7(this.io.workDir, download.id);
|
|
57304
|
+
await rm5(work, { recursive: true, force: true, maxRetries: 3 });
|
|
57305
|
+
const unpacked = join7(work, "unpacked");
|
|
57306
|
+
await mkdir7(unpacked, { recursive: true });
|
|
57307
|
+
const zipFile = join7(work, "download.zip");
|
|
57308
|
+
progress.state = "downloading";
|
|
57309
|
+
await this.download(download, zipFile, progress);
|
|
57310
|
+
progress.state = "unpacking";
|
|
57311
|
+
await this.io.unzip(zipFile, unpacked).catch((error) => {
|
|
57312
|
+
console.error(`[studio] Set up this computer: unpacking ${download.name}: ${error instanceof Error ? error.message : String(error)}`);
|
|
57313
|
+
throw new PlainError(`Ariadne could not unpack ${download.name}. Check that the disk has free space and press Try again.`);
|
|
57314
|
+
});
|
|
57315
|
+
const [folder, ...others] = await readdir5(unpacked);
|
|
57316
|
+
if (!folder || others.length > 0) throw new PlainError(`The download of ${download.name} was not what Ariadne expected, so it was not used.`);
|
|
57317
|
+
const destination = this.io.destination(download.id);
|
|
57318
|
+
await mkdir7(dirname6(destination), { recursive: true });
|
|
57319
|
+
await rm5(destination, { recursive: true, force: true, maxRetries: 3 });
|
|
57320
|
+
await moveFolder(join7(unpacked, folder), destination);
|
|
57321
|
+
progress.state = "checking";
|
|
57322
|
+
if (download.id === "maestro" && this.tools.find((tool) => tool.id === "java")?.state === "failed") return;
|
|
57323
|
+
await this.io.start(join7(destination, download.check.file), download.check.args).catch((error) => {
|
|
57324
|
+
console.error(`[studio] Set up this computer: starting ${download.name}: ${error instanceof Error ? error.message : String(error)}`);
|
|
57325
|
+
if (["EACCES", "EPERM", "UNKNOWN"].includes(String(codeOf(error)))) {
|
|
57326
|
+
throw new PlainError(
|
|
57327
|
+
`Ariadne downloaded ${download.name}, but Windows would not start it. A company computer may block programs in your user folder: ask your IT team to allow ${destination}.`
|
|
57328
|
+
);
|
|
57329
|
+
}
|
|
57330
|
+
throw new PlainError(`Ariadne downloaded ${download.name}, but it did not start correctly. Press Try again. If it keeps failing, Help > Show logs has the reason.`);
|
|
57331
|
+
});
|
|
57332
|
+
}
|
|
57333
|
+
async download(download, file, progress) {
|
|
57334
|
+
const aborter = new AbortController();
|
|
57335
|
+
let stall = setTimeout(() => aborter.abort(), STALL_MS);
|
|
57336
|
+
try {
|
|
57337
|
+
const response = await this.io.fetch(download.url, { signal: aborter.signal });
|
|
57338
|
+
if (!response.ok || !response.body) throw new Error(`${download.url} answered ${response.status}`);
|
|
57339
|
+
const hash = createHash("sha256");
|
|
57340
|
+
const meter = new Transform({
|
|
57341
|
+
transform(chunk2, _encoding, done) {
|
|
57342
|
+
clearTimeout(stall);
|
|
57343
|
+
stall = setTimeout(() => aborter.abort(), STALL_MS);
|
|
57344
|
+
hash.update(chunk2);
|
|
57345
|
+
progress.received += chunk2.length;
|
|
57346
|
+
done(null, chunk2);
|
|
57347
|
+
}
|
|
57348
|
+
});
|
|
57349
|
+
await pipeline(Readable.fromWeb(response.body), meter, createWriteStream(file));
|
|
57350
|
+
if (hash.digest("hex") !== download.sha256) {
|
|
57351
|
+
throw new PlainError(`The download of ${download.name} did not match what Ariadne expected, so it was not used. Press Try again.`);
|
|
57352
|
+
}
|
|
57353
|
+
} finally {
|
|
57354
|
+
clearTimeout(stall);
|
|
57355
|
+
}
|
|
57356
|
+
}
|
|
57357
|
+
};
|
|
56569
57358
|
|
|
56570
57359
|
// ../studio/src/server/setup.ts
|
|
56571
|
-
import { execFile as execFile4, spawn as spawn3 } from "node:child_process";
|
|
56572
|
-
import { existsSync as existsSync3 } from "node:fs";
|
|
56573
|
-
import { delimiter, join as join7 } from "node:path";
|
|
56574
57360
|
var EXEC_TIMEOUT_MS = 1e4;
|
|
56575
57361
|
function defaultSetupTools(androidHome2, findMaestro2) {
|
|
56576
57362
|
return {
|
|
56577
57363
|
exec: (file, args) => new Promise((resolvePromise, reject) => {
|
|
56578
|
-
|
|
57364
|
+
execFile5(file, args, { timeout: EXEC_TIMEOUT_MS, windowsHide: true }, (error, stdout, stderr) => {
|
|
56579
57365
|
if (error) reject(error);
|
|
56580
57366
|
else resolvePromise({ stdout, stderr });
|
|
56581
57367
|
});
|
|
@@ -56587,9 +57373,14 @@ function defaultSetupTools(androidHome2, findMaestro2) {
|
|
|
56587
57373
|
},
|
|
56588
57374
|
androidHome: androidHome2,
|
|
56589
57375
|
findMaestro: findMaestro2,
|
|
57376
|
+
javaBin: () => {
|
|
57377
|
+
const home = toolsJavaHome();
|
|
57378
|
+
return home ? join8(home, "bin", "java.exe") : "java";
|
|
57379
|
+
},
|
|
57380
|
+
installer: new ToolInstaller(defaultInstallIo(androidHome2)),
|
|
56590
57381
|
platform: process.platform,
|
|
56591
57382
|
path: process.env.PATH ?? "",
|
|
56592
|
-
exists:
|
|
57383
|
+
exists: existsSync4
|
|
56593
57384
|
};
|
|
56594
57385
|
}
|
|
56595
57386
|
function osName(platform) {
|
|
@@ -56601,9 +57392,9 @@ function osName(platform) {
|
|
|
56601
57392
|
function findProgram(tools, sdkDir, name) {
|
|
56602
57393
|
const file = tools.platform === "win32" ? `${name}.exe` : name;
|
|
56603
57394
|
const home = tools.androidHome();
|
|
56604
|
-
const candidates = home ? [
|
|
57395
|
+
const candidates = home ? [join8(home, sdkDir, file)] : [];
|
|
56605
57396
|
for (const dir of tools.path.split(delimiter)) {
|
|
56606
|
-
if (dir) candidates.push(
|
|
57397
|
+
if (dir) candidates.push(join8(dir, file));
|
|
56607
57398
|
}
|
|
56608
57399
|
return candidates.find((candidate) => tools.exists(candidate));
|
|
56609
57400
|
}
|
|
@@ -56638,7 +57429,7 @@ function parseAvdList(output) {
|
|
|
56638
57429
|
}
|
|
56639
57430
|
async function checkJava(tools) {
|
|
56640
57431
|
try {
|
|
56641
|
-
const { stdout, stderr } = await tools.exec(
|
|
57432
|
+
const { stdout, stderr } = await tools.exec(tools.javaBin(), ["-version"]);
|
|
56642
57433
|
const parsed = parseJavaVersion(`${stderr}
|
|
56643
57434
|
${stdout}`);
|
|
56644
57435
|
return parsed ? { found: true, version: parsed.version, major: parsed.major } : { found: true };
|
|
@@ -56706,6 +57497,20 @@ async function checkSetup(tools) {
|
|
|
56706
57497
|
}
|
|
56707
57498
|
function registerSetupRoutes(app, tools) {
|
|
56708
57499
|
app.get("/api/setup", async () => checkSetup(tools));
|
|
57500
|
+
app.get("/api/setup/install", async () => tools.installer.status());
|
|
57501
|
+
app.post("/api/setup/install", async (request) => {
|
|
57502
|
+
if (!tools.installer.status().available) {
|
|
57503
|
+
throw new HttpError(409, "CANNOT_INSTALL", "Ariadne cannot fetch the tools on this computer. Follow the manual steps in Device setup.");
|
|
57504
|
+
}
|
|
57505
|
+
if (!tools.installer.status().running) {
|
|
57506
|
+
const missing = missingTools(await checkSetup(tools));
|
|
57507
|
+
if (missing.includes("adb") && (request.body ?? {}).acceptTerms !== true) {
|
|
57508
|
+
throw new HttpError(400, "TERMS_NOT_ACCEPTED", "The Android tools need Google's Android SDK terms accepted first.");
|
|
57509
|
+
}
|
|
57510
|
+
tools.installer.start(missing);
|
|
57511
|
+
}
|
|
57512
|
+
return tools.installer.status();
|
|
57513
|
+
});
|
|
56709
57514
|
app.post("/api/setup/emulator", async (request) => {
|
|
56710
57515
|
const name = (request.body ?? {}).name;
|
|
56711
57516
|
if (typeof name !== "string" || !name.trim()) throw new HttpError(400, "BAD_REQUEST", 'Expected the "name" of an emulator.');
|
|
@@ -56798,11 +57603,11 @@ function cleaner({ env, typed, phones, paths }) {
|
|
|
56798
57603
|
};
|
|
56799
57604
|
}
|
|
56800
57605
|
async function deviceLogLines(dir) {
|
|
56801
|
-
const entries = await
|
|
57606
|
+
const entries = await readdir6(dir, { withFileTypes: true, recursive: true }).catch(() => []);
|
|
56802
57607
|
const logs = entries.filter((entry) => entry.isFile() && entry.name === "device-logcat.txt");
|
|
56803
57608
|
const lines = [];
|
|
56804
57609
|
for (const entry of logs) {
|
|
56805
|
-
const text = await
|
|
57610
|
+
const text = await readFile5(join9(entry.parentPath, entry.name), "utf8").catch(() => "");
|
|
56806
57611
|
lines.push(...text.split(/\r?\n/).filter((line) => DEVICE_LOG_LINES.test(line)));
|
|
56807
57612
|
}
|
|
56808
57613
|
return lines.length > 0 ? lines.slice(-MAX_DEVICE_LOG_LINES).join("\n") : void 0;
|
|
@@ -56817,8 +57622,8 @@ async function runEntries(state, caseIds, clean) {
|
|
|
56817
57622
|
const dir = state.project.runDir(run2.runId);
|
|
56818
57623
|
entries.push({ name: `runs/${run2.runId}/summary.json`, data: clean(JSON.stringify(run2, withoutTypedText, 2)) });
|
|
56819
57624
|
for (const file of ["output.txt", "maestro.log"]) {
|
|
56820
|
-
const path =
|
|
56821
|
-
if (await exists3(path)) entries.push({ name: `runs/${run2.runId}/${file}`, data: clean(await
|
|
57625
|
+
const path = join9(dir, file);
|
|
57626
|
+
if (await exists3(path)) entries.push({ name: `runs/${run2.runId}/${file}`, data: clean(await readFile5(path, "utf8")) });
|
|
56822
57627
|
}
|
|
56823
57628
|
const deviceLog = await deviceLogLines(dir);
|
|
56824
57629
|
if (deviceLog) entries.push({ name: `runs/${run2.runId}/device-log-filtered.txt`, data: clean(deviceLog) });
|
|
@@ -56828,7 +57633,7 @@ async function runEntries(state, caseIds, clean) {
|
|
|
56828
57633
|
}
|
|
56829
57634
|
async function readProjectFile(state, path) {
|
|
56830
57635
|
try {
|
|
56831
|
-
return await
|
|
57636
|
+
return await readFile5(state.project.path(path));
|
|
56832
57637
|
} catch {
|
|
56833
57638
|
return void 0;
|
|
56834
57639
|
}
|
|
@@ -56883,7 +57688,7 @@ async function buildDiagnostics(state, options, screenshots) {
|
|
|
56883
57688
|
{ name: "README.txt", data: README },
|
|
56884
57689
|
{ name: "summary.json", data: clean(JSON.stringify(summary, null, 2)) },
|
|
56885
57690
|
{ name: "log.txt", data: clean((state.log?.lines() ?? []).join("\n")) },
|
|
56886
|
-
...await exists3(sessionsFile) ? [{ name: "phone-sessions.jsonl", data: clean(await
|
|
57691
|
+
...await exists3(sessionsFile) ? [{ name: "phone-sessions.jsonl", data: clean(await readFile5(sessionsFile, "utf8")) }] : [],
|
|
56887
57692
|
...cases.map((testCase) => ({ name: `cases/${testCase.id}.json`, data: clean(JSON.stringify(plainCase(testCase), null, 2)) })),
|
|
56888
57693
|
...await runEntries(state, summaries.map((item) => item.id), clean),
|
|
56889
57694
|
...screenshots ? await screenshotEntries(state, cases) : []
|
|
@@ -56941,16 +57746,242 @@ function captureLog(limit = KEPT_LINES) {
|
|
|
56941
57746
|
}
|
|
56942
57747
|
|
|
56943
57748
|
// ../studio/src/server/routes.ts
|
|
56944
|
-
import { copyFile as
|
|
56945
|
-
import { dirname as
|
|
57749
|
+
import { copyFile as copyFile3, mkdir as mkdir12, readdir as readdir8, rm as rm9 } from "node:fs/promises";
|
|
57750
|
+
import { dirname as dirname9, join as join13 } from "node:path";
|
|
57751
|
+
|
|
57752
|
+
// ../studio/src/shared/api-types.ts
|
|
57753
|
+
var MAX_WAIT_SECONDS = 120;
|
|
57754
|
+
|
|
57755
|
+
// ../studio/src/shared/block-check.ts
|
|
57756
|
+
var MAX_NAME = 80;
|
|
57757
|
+
var MAX_LINK = 2e3;
|
|
57758
|
+
var ONLY_A_REFERENCE = /^\$\{[A-Za-z_][A-Za-z0-9_]*\}$/;
|
|
57759
|
+
var NO_NAME = "${}";
|
|
57760
|
+
function isOpenableLink(url) {
|
|
57761
|
+
return url.length <= MAX_LINK && /^[a-zA-Z][a-zA-Z0-9+.-]*:\/\/\S+$/.test(url) && !/^(file|content|javascript):/i.test(url);
|
|
57762
|
+
}
|
|
57763
|
+
function textProblem(text, missing) {
|
|
57764
|
+
if (!text.trim()) return `needs ${missing}`;
|
|
57765
|
+
if (text === NO_NAME) return "needs a name for its setting";
|
|
57766
|
+
if (/[\r\n]/.test(text)) return "must fit on one line";
|
|
57767
|
+
return void 0;
|
|
57768
|
+
}
|
|
57769
|
+
function selectorProblem(selector, missing, canBePoint) {
|
|
57770
|
+
if (selector.kind !== "point") return textProblem(selector.value, missing);
|
|
57771
|
+
return canBePoint ? void 0 : `needs ${missing}, and a point on the screen cannot be looked for`;
|
|
57772
|
+
}
|
|
57773
|
+
function stepProblem(step) {
|
|
57774
|
+
switch (step.kind) {
|
|
57775
|
+
case "tapOn":
|
|
57776
|
+
case "longPressOn":
|
|
57777
|
+
case "doubleTapOn":
|
|
57778
|
+
return selectorProblem(step.selector, "something to press", true);
|
|
57779
|
+
case "assertVisible":
|
|
57780
|
+
case "assertNotVisible":
|
|
57781
|
+
case "scrollUntilVisible":
|
|
57782
|
+
return selectorProblem(step.selector, "something to look for", false);
|
|
57783
|
+
case "inputText":
|
|
57784
|
+
return textProblem(step.text, "the text to type");
|
|
57785
|
+
case "openLink":
|
|
57786
|
+
if (textProblem(step.link, "a link")) return textProblem(step.link, "a link");
|
|
57787
|
+
return ONLY_A_REFERENCE.test(step.link) || isOpenableLink(step.link) ? void 0 : "needs a link such as https://example.com/claims or myapp://claims/123";
|
|
57788
|
+
case "wait":
|
|
57789
|
+
return Number.isInteger(step.seconds) && step.seconds >= 1 && step.seconds <= MAX_WAIT_SECONDS ? void 0 : `must wait 1 to ${MAX_WAIT_SECONDS} seconds`;
|
|
57790
|
+
case "eraseText":
|
|
57791
|
+
return step.chars === void 0 || Number.isInteger(step.chars) && step.chars >= 1 ? void 0 : "must erase at least 1 character";
|
|
57792
|
+
case "runFlow":
|
|
57793
|
+
return textProblem(step.file, "a flow file");
|
|
57794
|
+
default:
|
|
57795
|
+
return void 0;
|
|
57796
|
+
}
|
|
57797
|
+
}
|
|
57798
|
+
function blockProblem(name, steps) {
|
|
57799
|
+
if (!name.trim()) return "Give the block a name.";
|
|
57800
|
+
if (name.length > MAX_NAME || /[\r\n]/.test(name)) return `A block's name fits on one line of at most ${MAX_NAME} characters.`;
|
|
57801
|
+
if (steps.length === 0) return "Add at least one step.";
|
|
57802
|
+
for (const [position, step] of steps.entries()) {
|
|
57803
|
+
const problem = stepProblem(step);
|
|
57804
|
+
if (problem) return `Step ${position + 1} ${problem}.`;
|
|
57805
|
+
}
|
|
57806
|
+
return void 0;
|
|
57807
|
+
}
|
|
57808
|
+
function hideSavedValues(steps, env) {
|
|
57809
|
+
return steps.map((step) => {
|
|
57810
|
+
if (step.kind !== "inputText") return step;
|
|
57811
|
+
const text = normalizeValues({ text: step.text }, env)?.text;
|
|
57812
|
+
return text === void 0 ? step : { ...step, text };
|
|
57813
|
+
});
|
|
57814
|
+
}
|
|
57815
|
+
|
|
57816
|
+
// ../studio/src/server/blocks.ts
|
|
57817
|
+
import { readFile as readFile6, rm as rm6 } from "node:fs/promises";
|
|
57818
|
+
import { dirname as dirname7, resolve as resolve2 } from "node:path";
|
|
57819
|
+
var COMMENT = /^\s*#|\s#\s/m;
|
|
57820
|
+
async function readBlock(state, query) {
|
|
57821
|
+
const { file } = query ?? {};
|
|
57822
|
+
const block = (await state.project.listBlocks()).find((candidate) => candidate.file === file);
|
|
57823
|
+
if (!block) throw new HttpError(404, "UNKNOWN_BLOCK", `There is no block ${String(file)} in the project's block folders.`);
|
|
57824
|
+
return block;
|
|
57825
|
+
}
|
|
57826
|
+
function ownFlow(block) {
|
|
57827
|
+
const flow = block.outline?.ownFlow;
|
|
57828
|
+
if (!flow) {
|
|
57829
|
+
throw new HttpError(409, "BLOCK_NOT_EDITABLE", `${block.name} uses commands Ariadne does not read, so its file can only be changed by hand.`);
|
|
57830
|
+
}
|
|
57831
|
+
return flow;
|
|
57832
|
+
}
|
|
57833
|
+
async function blockDetail(state, block) {
|
|
57834
|
+
const flow = ownFlow(block);
|
|
57835
|
+
return {
|
|
57836
|
+
file: block.file,
|
|
57837
|
+
name: block.name,
|
|
57838
|
+
appId: flow.appId,
|
|
57839
|
+
steps: flow.steps,
|
|
57840
|
+
fixed: Object.keys(flow.env ?? {}),
|
|
57841
|
+
usedBy: await state.project.blockUsers(block.file),
|
|
57842
|
+
hasComments: COMMENT.test(await readFile6(state.project.blockPath(block.file), "utf8"))
|
|
57843
|
+
};
|
|
57844
|
+
}
|
|
57845
|
+
function readSave(body) {
|
|
57846
|
+
const { name, steps, appId } = body ?? {};
|
|
57847
|
+
if (typeof name !== "string" || !Array.isArray(steps) || appId !== void 0 && typeof appId !== "string") {
|
|
57848
|
+
throw new HttpError(400, "BAD_BLOCK", "Expected { name, steps } with the steps as a list.");
|
|
57849
|
+
}
|
|
57850
|
+
return { name: name.trim(), steps, ...appId !== void 0 ? { appId: appId.trim() } : {} };
|
|
57851
|
+
}
|
|
57852
|
+
async function checkedFlow(state, header, steps) {
|
|
57853
|
+
let flow;
|
|
57854
|
+
try {
|
|
57855
|
+
flow = parseMaestroFlow(toMaestroYaml({ ...header, steps }));
|
|
57856
|
+
} catch (error) {
|
|
57857
|
+
throw new HttpError(400, "BAD_BLOCK", `These steps cannot be saved as a block: ${error instanceof Error ? error.message : String(error)}`);
|
|
57858
|
+
}
|
|
57859
|
+
const problem = blockProblem(flow.name ?? "", flow.steps);
|
|
57860
|
+
if (problem) throw new HttpError(400, "BAD_BLOCK", problem);
|
|
57861
|
+
return { ...flow, steps: hideSavedValues(flow.steps, await projectEnv(state)) };
|
|
57862
|
+
}
|
|
57863
|
+
async function refuseOtherFlows(state, file, steps, existing) {
|
|
57864
|
+
const kept = new Set(existing.map((step) => JSON.stringify(step)));
|
|
57865
|
+
const blocks = (await state.project.listBlocks()).filter((block) => !block.error);
|
|
57866
|
+
const working = new Set(blocks.map((block) => state.project.blockPath(block.file)));
|
|
57867
|
+
const folder = dirname7(state.project.blockPath(file));
|
|
57868
|
+
for (const step of steps) {
|
|
57869
|
+
if (step.kind !== "runFlow" || kept.has(JSON.stringify(step)) || working.has(resolve2(folder, step.file))) continue;
|
|
57870
|
+
throw new HttpError(400, "BAD_BLOCK", `A block can only run another working block of this project, and ${step.file} is not one.`);
|
|
57871
|
+
}
|
|
57872
|
+
}
|
|
57873
|
+
async function refuseLoop(state, file, steps) {
|
|
57874
|
+
if (await state.project.reachesBlock(file, steps)) {
|
|
57875
|
+
throw new HttpError(400, "BAD_BLOCK", "A block cannot run itself, directly or through a block it runs. Remove the step that leads back to it.");
|
|
57876
|
+
}
|
|
57877
|
+
}
|
|
57878
|
+
function chainSteps(chain, paths) {
|
|
57879
|
+
return [...chain].reverse().flatMap((testCase, position) => caseToFlow(testCase, [], paths).steps.slice(position === 0 ? 0 : 1));
|
|
57880
|
+
}
|
|
57881
|
+
async function savedBlock(state, file) {
|
|
57882
|
+
return blockDetail(state, await readBlock(state, { file }));
|
|
57883
|
+
}
|
|
57884
|
+
function registerBlockRoutes(app, state) {
|
|
57885
|
+
app.get("/api/blocks/detail", async (request) => blockDetail(state, await readBlock(state, request.query)));
|
|
57886
|
+
app.post("/api/blocks", async (request) => {
|
|
57887
|
+
const { name, steps, appId } = readSave(request.body);
|
|
57888
|
+
if (!appId) throw new HttpError(400, "BAD_BLOCK", "Enter the app id under Device first: a block says which app it is for.");
|
|
57889
|
+
return enqueue(state, async () => {
|
|
57890
|
+
requireNoRecording(state);
|
|
57891
|
+
const flow = await checkedFlow(state, { appId, name }, steps);
|
|
57892
|
+
try {
|
|
57893
|
+
const file = await state.project.newBlockFile(name);
|
|
57894
|
+
await refuseOtherFlows(state, file, flow.steps, []);
|
|
57895
|
+
await refuseLoop(state, file, flow.steps);
|
|
57896
|
+
return await savedBlock(state, await state.project.writeBlock(flow, file));
|
|
57897
|
+
} catch (error) {
|
|
57898
|
+
if (error instanceof HttpError) throw error;
|
|
57899
|
+
throw new HttpError(409, "BAD_BLOCK", error instanceof Error ? error.message : String(error));
|
|
57900
|
+
}
|
|
57901
|
+
});
|
|
57902
|
+
});
|
|
57903
|
+
app.put("/api/blocks/detail", async (request) => {
|
|
57904
|
+
const { name, steps } = readSave(request.body);
|
|
57905
|
+
return enqueue(state, async () => {
|
|
57906
|
+
requireNoRecording(state);
|
|
57907
|
+
const block = await readBlock(state, request.query);
|
|
57908
|
+
const { appId, env, steps: existing } = ownFlow(block);
|
|
57909
|
+
const flow = await checkedFlow(state, { appId, name, ...env ? { env } : {} }, steps);
|
|
57910
|
+
await refuseOtherFlows(state, block.file, flow.steps, existing);
|
|
57911
|
+
await refuseLoop(state, block.file, flow.steps);
|
|
57912
|
+
await state.project.writeBlock(flow, block.file);
|
|
57913
|
+
return savedBlock(state, block.file);
|
|
57914
|
+
});
|
|
57915
|
+
});
|
|
57916
|
+
app.post("/api/blocks/record", async (request) => {
|
|
57917
|
+
const { name } = request.body ?? {};
|
|
57918
|
+
if (typeof name !== "string" || !name.trim()) throw new HttpError(400, "BAD_REQUEST", "Expected the name of the block.");
|
|
57919
|
+
requireSession(state);
|
|
57920
|
+
return enqueue(state, async () => {
|
|
57921
|
+
requireNoRecording(state);
|
|
57922
|
+
const id = await state.project.allocateCaseId("block");
|
|
57923
|
+
const screen = toScreenState(await captureScratch(state));
|
|
57924
|
+
const recorder = new Recorder();
|
|
57925
|
+
recorder.start({ id, name: name.trim(), appId: state.session.appId, deviceId: state.session.deviceId, envKeys: await state.project.envKeys() });
|
|
57926
|
+
state.recorder = recorder;
|
|
57927
|
+
state.editing = void 0;
|
|
57928
|
+
state.recordingBlock = true;
|
|
57929
|
+
return { caseId: id, screen };
|
|
57930
|
+
});
|
|
57931
|
+
});
|
|
57932
|
+
app.post("/api/blocks/record/finish", async (request) => {
|
|
57933
|
+
const picks = readPicks(request.body);
|
|
57934
|
+
const recorder = requireCase(state);
|
|
57935
|
+
if (!state.recordingBlock) throw new HttpError(409, "NOT_RECORDING_BLOCK", "This recording is a case. Finish it from the Record tab.");
|
|
57936
|
+
const { file } = request.body ?? {};
|
|
57937
|
+
const block = file === void 0 ? void 0 : await readBlock(state, { file });
|
|
57938
|
+
await stopPhoneCapture(state);
|
|
57939
|
+
return enqueue(state, async () => {
|
|
57940
|
+
requireRecording(state, recorder);
|
|
57941
|
+
for (const candidate of pickedCandidates(state, picks)) recorder.addExpectation({ selector: candidate.selector, node: candidate.node });
|
|
57942
|
+
const testCase = recorder.toCase();
|
|
57943
|
+
const steps = caseToFlow(testCase, [], state.project.blockFlowPaths(block?.file)).steps.slice(1);
|
|
57944
|
+
state.recorder = void 0;
|
|
57945
|
+
state.recordingBlock = void 0;
|
|
57946
|
+
state.offered = void 0;
|
|
57947
|
+
await rm6(state.project.evidenceDir(testCase.id), { recursive: true, force: true, maxRetries: 3 }).catch((error) => {
|
|
57948
|
+
console.error(`[studio] could not remove the screenshots in ${state.project.relative(state.project.evidenceDir(testCase.id))}: ${error instanceof Error ? error.message : String(error)}`);
|
|
57949
|
+
});
|
|
57950
|
+
return { steps };
|
|
57951
|
+
});
|
|
57952
|
+
});
|
|
57953
|
+
app.get("/api/cases/:caseId/block-draft", async (request) => {
|
|
57954
|
+
const caseId = readId(request.params, "caseId");
|
|
57955
|
+
const { name, appId } = await readSavedCase(state, caseId);
|
|
57956
|
+
let chain;
|
|
57957
|
+
try {
|
|
57958
|
+
chain = await state.project.startChain(caseId);
|
|
57959
|
+
} catch (error) {
|
|
57960
|
+
throw new HttpError(409, "BROKEN_CHAIN", error instanceof Error ? error.message : String(error));
|
|
57961
|
+
}
|
|
57962
|
+
return { name, appId, steps: chainSteps(chain, state.project.blockFlowPaths()) };
|
|
57963
|
+
});
|
|
57964
|
+
app.delete("/api/blocks/detail", async (request) => {
|
|
57965
|
+
return enqueue(state, async () => {
|
|
57966
|
+
requireNoRecording(state);
|
|
57967
|
+
const block = await readBlock(state, request.query);
|
|
57968
|
+
const users = await state.project.blockUsers(block.file);
|
|
57969
|
+
if (users.length > 0) {
|
|
57970
|
+
throw new HttpError(409, "BLOCK_IN_USE", `${block.name} is used by ${users.join(", ")}. Take it out of those first, or keep it.`);
|
|
57971
|
+
}
|
|
57972
|
+
await state.project.deleteBlock(block.file);
|
|
57973
|
+
return { ok: true };
|
|
57974
|
+
});
|
|
57975
|
+
});
|
|
57976
|
+
}
|
|
56946
57977
|
|
|
56947
57978
|
// ../studio/src/server/cases.ts
|
|
56948
|
-
import { readFile as
|
|
57979
|
+
import { readFile as readFile8, rm as rm8 } from "node:fs/promises";
|
|
56949
57980
|
|
|
56950
57981
|
// ../studio/src/server/replay.ts
|
|
56951
57982
|
import { createReadStream as createReadStream2 } from "node:fs";
|
|
56952
|
-
import { mkdir as
|
|
56953
|
-
import { dirname as
|
|
57983
|
+
import { mkdir as mkdir8, readFile as readFile7, rename as rename4, rm as rm7, stat, writeFile as writeFile5 } from "node:fs/promises";
|
|
57984
|
+
import { dirname as dirname8, join as join10, sep as sep2 } from "node:path";
|
|
56954
57985
|
async function addSimilarity(state, testCase, summary) {
|
|
56955
57986
|
const items = caseFlowItems(testCase);
|
|
56956
57987
|
const steps = [];
|
|
@@ -56962,7 +57993,7 @@ async function addSimilarity(state, testCase, summary) {
|
|
|
56962
57993
|
continue;
|
|
56963
57994
|
}
|
|
56964
57995
|
try {
|
|
56965
|
-
const [a, b] = await Promise.all([
|
|
57996
|
+
const [a, b] = await Promise.all([readFile7(state.project.path(recorded)), readFile7(state.project.path(result.replayScreenshot))]);
|
|
56966
57997
|
steps.push({ ...result, similarity: screenSimilarity(decodePng(a), decodePng(b)) });
|
|
56967
57998
|
} catch {
|
|
56968
57999
|
steps.push(result);
|
|
@@ -56971,10 +58002,10 @@ async function addSimilarity(state, testCase, summary) {
|
|
|
56971
58002
|
return { ...summary, steps };
|
|
56972
58003
|
}
|
|
56973
58004
|
async function keepRunVideo(debugDir, flowDirName) {
|
|
56974
|
-
const saved =
|
|
58005
|
+
const saved = join10(debugDir, flowDirName, "startRecording", `${RUN_VIDEO_NAME}.mp4`);
|
|
56975
58006
|
if (!await exists3(saved)) return void 0;
|
|
56976
|
-
const kept =
|
|
56977
|
-
await
|
|
58007
|
+
const kept = join10(debugDir, "video.mp4");
|
|
58008
|
+
await rename4(saved, kept);
|
|
56978
58009
|
return kept;
|
|
56979
58010
|
}
|
|
56980
58011
|
async function frameVideo(state, testCase, summary, commands, device) {
|
|
@@ -57028,7 +58059,7 @@ async function runCase(state, caseId, options) {
|
|
|
57028
58059
|
const runId = await project.allocateRunId(caseId);
|
|
57029
58060
|
const debugDir = project.runDir(runId);
|
|
57030
58061
|
const ranFlowPath = `${debugDir}${sep2}flow.yaml`;
|
|
57031
|
-
await
|
|
58062
|
+
await mkdir8(debugDir, { recursive: true });
|
|
57032
58063
|
const { runVideo } = await project.readSettings();
|
|
57033
58064
|
await writeFile5(ranFlowPath, toMaestroYaml(instrumentForRun(flow, project.flowsDirFrom(debugDir), { video: runVideo === true })), "utf8");
|
|
57034
58065
|
console.log(`[studio] replay ${caseId} on ${options.deviceId}: running ${project.relative(ranFlowPath)}`);
|
|
@@ -57053,10 +58084,10 @@ async function runCase(state, caseId, options) {
|
|
|
57053
58084
|
const read = await readMaestroCommands(debugDir);
|
|
57054
58085
|
const video = runVideo && read ? await keepRunVideo(debugDir, read.flowDirName) : void 0;
|
|
57055
58086
|
const diagnostics = result.exitCode === 0 ? void 0 : [
|
|
57056
|
-
await
|
|
57057
|
-
await
|
|
58087
|
+
await readFile7(result.outputFile, "utf8").catch(() => ""),
|
|
58088
|
+
await readFile7(`${debugDir}${sep2}maestro.log`, "utf8").catch(() => ""),
|
|
57058
58089
|
// Android drops a blocked launch silently: only the device log shows it.
|
|
57059
|
-
read ? await
|
|
58090
|
+
read ? await readFile7(join10(debugDir, read.flowDirName, "logs", "device-logcat.txt"), "utf8").catch(() => "") : ""
|
|
57060
58091
|
].join("\n");
|
|
57061
58092
|
let summary = summarizeRun({
|
|
57062
58093
|
runId,
|
|
@@ -57103,13 +58134,13 @@ async function runPrefixWithMaestro(state, prefix) {
|
|
|
57103
58134
|
if (prefix.partial.startFrom) await writeChainFlows(state, prefix.partial.startFrom);
|
|
57104
58135
|
const env = await projectEnv(state);
|
|
57105
58136
|
const flowPath = project.partialFlowPath(prefix.caseId);
|
|
57106
|
-
const dir =
|
|
57107
|
-
const debugDir =
|
|
58137
|
+
const dir = dirname8(flowPath);
|
|
58138
|
+
const debugDir = join10(dir, `${prefix.caseId}-maestro`);
|
|
57108
58139
|
const written = await caseFlow(state, prefix.partial);
|
|
57109
58140
|
const skipped = prefix.skipLaunch ? 1 : 0;
|
|
57110
58141
|
const flow = relocateFlow({ ...written, steps: written.steps.slice(skipped) }, project.flowsDirFrom(dir));
|
|
57111
|
-
await
|
|
57112
|
-
await
|
|
58142
|
+
await rm7(debugDir, { recursive: true, force: true });
|
|
58143
|
+
await mkdir8(dir, { recursive: true });
|
|
57113
58144
|
await writeFile5(flowPath, toMaestroYaml(flow), "utf8");
|
|
57114
58145
|
console.log(`[studio] ${prefix.what} on ${deviceId}: running ${project.relative(flowPath)} with Maestro`);
|
|
57115
58146
|
let result;
|
|
@@ -57131,7 +58162,7 @@ async function runPrefixWithMaestro(state, prefix) {
|
|
|
57131
58162
|
} catch (error) {
|
|
57132
58163
|
throw new HttpError(500, "MAESTRO_FAILED", `Could not run Maestro: ${error instanceof Error ? error.message : String(error)}`);
|
|
57133
58164
|
} finally {
|
|
57134
|
-
await
|
|
58165
|
+
await rm7(flowPath, { force: true });
|
|
57135
58166
|
}
|
|
57136
58167
|
const read = await readMaestroCommands(debugDir);
|
|
57137
58168
|
const summary = summarizeRun({
|
|
@@ -57149,7 +58180,7 @@ async function runPrefixWithMaestro(state, prefix) {
|
|
|
57149
58180
|
});
|
|
57150
58181
|
await redactRunFiles(debugDir, env).catch(() => void 0);
|
|
57151
58182
|
if (summary.passed) {
|
|
57152
|
-
await
|
|
58183
|
+
await rm7(debugDir, { recursive: true, force: true });
|
|
57153
58184
|
console.log(`[studio] ${prefix.what}: Maestro passed after ${Math.round(result.durationMs / 1e3)} s`);
|
|
57154
58185
|
return;
|
|
57155
58186
|
}
|
|
@@ -57214,11 +58245,30 @@ async function failedStepText(state, testCase, summary) {
|
|
|
57214
58245
|
const blocks = await blockNames(state);
|
|
57215
58246
|
return describeFlowItem(item, testCase.appId, { caseName: (id) => names.get(id), blockName: (file) => blocks.get(file) });
|
|
57216
58247
|
}
|
|
58248
|
+
async function readTestedOn2(state, suite) {
|
|
58249
|
+
const appIds = [];
|
|
58250
|
+
for (const item of suite.cases) {
|
|
58251
|
+
if (item.outcome === "muted") continue;
|
|
58252
|
+
const testCase = await state.project.readCase(item.caseId).catch(() => void 0);
|
|
58253
|
+
if (testCase) appIds.push(testCase.appId);
|
|
58254
|
+
}
|
|
58255
|
+
try {
|
|
58256
|
+
return await state.phoneTools.testedOn(suite.deviceId, [...new Set(appIds)]);
|
|
58257
|
+
} catch (error) {
|
|
58258
|
+
console.error(`[studio] could not read the phone and app versions for ${suite.suiteId}: ${error instanceof Error ? error.message : String(error)}`);
|
|
58259
|
+
return void 0;
|
|
58260
|
+
}
|
|
58261
|
+
}
|
|
57217
58262
|
async function runSuite(state, job) {
|
|
57218
58263
|
const { project } = state;
|
|
57219
58264
|
const suite = job.run;
|
|
57220
58265
|
const save = () => project.saveSuite(suite).catch((error) => console.error(`[studio] could not save ${suite.suiteId}:`, error));
|
|
57221
58266
|
await save();
|
|
58267
|
+
const testedOn = await readTestedOn2(state, suite);
|
|
58268
|
+
if (testedOn) {
|
|
58269
|
+
suite.testedOn = testedOn;
|
|
58270
|
+
await save();
|
|
58271
|
+
}
|
|
57222
58272
|
try {
|
|
57223
58273
|
await withSessionHandedToMaestro(state, async () => {
|
|
57224
58274
|
for (const item of suite.cases) {
|
|
@@ -57275,7 +58325,7 @@ async function runSuite(state, job) {
|
|
|
57275
58325
|
}
|
|
57276
58326
|
async function imageDataUrl(state, projectRelative, maxWidth = 540) {
|
|
57277
58327
|
try {
|
|
57278
|
-
const bytes = await
|
|
58328
|
+
const bytes = await readFile7(state.project.path(projectRelative));
|
|
57279
58329
|
let png = bytes;
|
|
57280
58330
|
try {
|
|
57281
58331
|
const image = decodePng(bytes);
|
|
@@ -57289,10 +58339,10 @@ async function imageDataUrl(state, projectRelative, maxWidth = 540) {
|
|
|
57289
58339
|
return void 0;
|
|
57290
58340
|
}
|
|
57291
58341
|
}
|
|
57292
|
-
async function imageMap(state, paths) {
|
|
58342
|
+
async function imageMap(state, paths, maxWidth) {
|
|
57293
58343
|
const map = /* @__PURE__ */ new Map();
|
|
57294
58344
|
for (const path of new Set(paths.filter((item) => Boolean(item)))) {
|
|
57295
|
-
const url = await imageDataUrl(state, path);
|
|
58345
|
+
const url = await imageDataUrl(state, path, maxWidth);
|
|
57296
58346
|
if (url) map.set(path, url);
|
|
57297
58347
|
}
|
|
57298
58348
|
return (path) => map.get(path);
|
|
@@ -57477,7 +58527,7 @@ function optionalText(body, key, maxLength) {
|
|
|
57477
58527
|
}
|
|
57478
58528
|
async function caseDetail(state, caseId) {
|
|
57479
58529
|
const testCase = await readSavedCase(state, caseId);
|
|
57480
|
-
const yaml = await
|
|
58530
|
+
const yaml = await readFile8(state.project.flowPath(caseId), "utf8").catch(() => "");
|
|
57481
58531
|
const summaries = await state.project.listRuns(caseId);
|
|
57482
58532
|
const all = await state.project.listCases();
|
|
57483
58533
|
const ref = (id) => ({ id, name: all.find((item) => item.id === id)?.name ?? id });
|
|
@@ -57520,6 +58570,7 @@ function registerCaseRoutes(app, state) {
|
|
|
57520
58570
|
refuseIfRecording(state, caseId);
|
|
57521
58571
|
const testCase = await readSavedCase(state, caseId);
|
|
57522
58572
|
const name = optionalText(request.body, "name", 200);
|
|
58573
|
+
const reference2 = optionalText(request.body, "reference", 100);
|
|
57523
58574
|
const muted = request.body?.muted;
|
|
57524
58575
|
if (muted !== void 0 && typeof muted !== "boolean") throw new HttpError(400, "BAD_REQUEST", 'Expected "muted" to be true or false.');
|
|
57525
58576
|
if (name !== void 0) {
|
|
@@ -57528,6 +58579,8 @@ function registerCaseRoutes(app, state) {
|
|
|
57528
58579
|
}
|
|
57529
58580
|
if (muted === true) testCase.muted = true;
|
|
57530
58581
|
if (muted === false) delete testCase.muted;
|
|
58582
|
+
if (reference2) testCase.reference = reference2;
|
|
58583
|
+
else if (reference2 !== void 0) delete testCase.reference;
|
|
57531
58584
|
await saveEdited(state, testCase, false);
|
|
57532
58585
|
return caseDetail(state, caseId);
|
|
57533
58586
|
});
|
|
@@ -57560,7 +58613,7 @@ function registerCaseRoutes(app, state) {
|
|
|
57560
58613
|
const [removed] = testCase.steps.splice(index - 1, 1);
|
|
57561
58614
|
testCase.steps = renumber(testCase.steps);
|
|
57562
58615
|
if (removed?.screenshot && !testCase.steps.some((step) => step.screenshot === removed.screenshot)) {
|
|
57563
|
-
await
|
|
58616
|
+
await rm8(state.project.path(removed.screenshot), { force: true });
|
|
57564
58617
|
}
|
|
57565
58618
|
await saveEdited(state, testCase, true);
|
|
57566
58619
|
return caseDetail(state, caseId);
|
|
@@ -57661,6 +58714,7 @@ function registerCaseRoutes(app, state) {
|
|
|
57661
58714
|
const partial = { ...testCase, steps: testCase.steps.slice(0, kept), expectations: [] };
|
|
57662
58715
|
const withMaestro = await replayNeedsMaestro(state, partial);
|
|
57663
58716
|
return enqueue(state, async () => {
|
|
58717
|
+
requireNoRecording(state);
|
|
57664
58718
|
const { project, session } = state;
|
|
57665
58719
|
const envKeys = await project.envKeys();
|
|
57666
58720
|
if (withMaestro) {
|
|
@@ -57678,7 +58732,7 @@ function registerCaseRoutes(app, state) {
|
|
|
57678
58732
|
const described = describeDeviceError(error);
|
|
57679
58733
|
throw new HttpError(502, described.code, `Running the case up to step ${kept} failed: ${described.message}`);
|
|
57680
58734
|
} finally {
|
|
57681
|
-
await
|
|
58735
|
+
await rm8(partialPath, { force: true });
|
|
57682
58736
|
}
|
|
57683
58737
|
}
|
|
57684
58738
|
const screen = toScreenState(await captureScratch(state));
|
|
@@ -57702,7 +58756,8 @@ function registerCaseRoutes(app, state) {
|
|
|
57702
58756
|
expectations: mode === "insert" ? testCase.expectations : [],
|
|
57703
58757
|
evidenceFloor: floor,
|
|
57704
58758
|
editedAt: nowIso(),
|
|
57705
|
-
...testCase.muted ? { muted: true } : {}
|
|
58759
|
+
...testCase.muted ? { muted: true } : {},
|
|
58760
|
+
...testCase.reference ? { reference: testCase.reference } : {}
|
|
57706
58761
|
});
|
|
57707
58762
|
state.recorder = recorder;
|
|
57708
58763
|
state.editing = {
|
|
@@ -57718,9 +58773,116 @@ function registerCaseRoutes(app, state) {
|
|
|
57718
58773
|
});
|
|
57719
58774
|
}
|
|
57720
58775
|
|
|
58776
|
+
// ../studio/src/server/evidence.ts
|
|
58777
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
58778
|
+
import { mkdir as mkdir10, readFile as readFile9, writeFile as writeFile7 } from "node:fs/promises";
|
|
58779
|
+
import { join as join11, relative as relative2, sep as sep3 } from "node:path";
|
|
58780
|
+
var SCREEN_WIDTH = 300;
|
|
58781
|
+
var MAX_LINE = 200;
|
|
58782
|
+
var MAX_ROLE = 40;
|
|
58783
|
+
var MAX_NOTES = 1e3;
|
|
58784
|
+
function readText(query, key, maxLength, multiline = false) {
|
|
58785
|
+
const value = query?.[key];
|
|
58786
|
+
if (value === void 0) return void 0;
|
|
58787
|
+
if (typeof value !== "string") throw new HttpError(400, "BAD_REQUEST", `${key} must be text.`);
|
|
58788
|
+
const unified = value.replace(/\r\n?/g, "\n");
|
|
58789
|
+
const text = (multiline ? unified.replace(/[\u0000-\u0009\u000b-\u001f\u007f]+/g, " ") : unified.replace(/[\u0000-\u001f\u007f]+/g, " ")).trim();
|
|
58790
|
+
if (text.length > maxLength) throw new HttpError(400, "BAD_REQUEST", `${key} must be at most ${maxLength} characters.`);
|
|
58791
|
+
return text || void 0;
|
|
58792
|
+
}
|
|
58793
|
+
function readScreens(query) {
|
|
58794
|
+
const value = query?.screens;
|
|
58795
|
+
if (value === void 0 || value === "all") return "all";
|
|
58796
|
+
if (value === "checks") return "checks";
|
|
58797
|
+
throw new HttpError(400, "BAD_REQUEST", 'screens must be "all" or "checks".');
|
|
58798
|
+
}
|
|
58799
|
+
function recordOf(state, file, bytes) {
|
|
58800
|
+
return { path: relative2(state.project.dir, file).split(sep3).join("/"), sha256: createHash2("sha256").update(bytes).digest("hex") };
|
|
58801
|
+
}
|
|
58802
|
+
function registerEvidenceRoutes(app, state) {
|
|
58803
|
+
app.get("/api/suites/:suiteId/evidence", async (request, reply) => {
|
|
58804
|
+
const suiteId = readId(request.params, "suiteId");
|
|
58805
|
+
const release2 = readText(request.query, "release", MAX_LINE);
|
|
58806
|
+
const testedBy = readText(request.query, "testedBy", MAX_LINE);
|
|
58807
|
+
const signoffRole = readText(request.query, "signoffRole", MAX_ROLE);
|
|
58808
|
+
const knownIssues = readText(request.query, "knownIssues", MAX_NOTES, true);
|
|
58809
|
+
const conclusion = readText(request.query, "conclusion", MAX_LINE);
|
|
58810
|
+
const screens = readScreens(request.query);
|
|
58811
|
+
if (state.suite?.run.suiteId === suiteId && state.suite.run.status === "running") {
|
|
58812
|
+
throw new HttpError(409, "SUITE_RUNNING", "Run all is still running. Make the evidence pack when it ends.");
|
|
58813
|
+
}
|
|
58814
|
+
const suiteFile = state.project.suiteFile(suiteId);
|
|
58815
|
+
const suiteBytes = await readFile9(suiteFile).catch(() => void 0);
|
|
58816
|
+
let suite;
|
|
58817
|
+
try {
|
|
58818
|
+
suite = suiteBytes ? JSON.parse(suiteBytes.toString("utf8")) : void 0;
|
|
58819
|
+
} catch {
|
|
58820
|
+
suite = void 0;
|
|
58821
|
+
}
|
|
58822
|
+
if (!suiteBytes || !suite) throw new HttpError(404, "NOT_FOUND", `No suite run ${suiteId}.`);
|
|
58823
|
+
const names = await reportNames(state);
|
|
58824
|
+
const cases = [];
|
|
58825
|
+
for (const item of suite.cases) {
|
|
58826
|
+
const testCase = await state.project.readCase(item.caseId).catch(() => void 0);
|
|
58827
|
+
const lastRunId = item.runIds.at(-1);
|
|
58828
|
+
const run2 = lastRunId ? await state.project.readRun(lastRunId).catch(() => void 0) : void 0;
|
|
58829
|
+
const summaryFile = lastRunId ? join11(state.project.runDir(lastRunId), "summary.json") : void 0;
|
|
58830
|
+
const summaryBytes = run2 && summaryFile ? await readFile9(summaryFile).catch(() => void 0) : void 0;
|
|
58831
|
+
const error = item.error ?? (run2 && !run2.passed ? run2.error : void 0);
|
|
58832
|
+
cases.push({
|
|
58833
|
+
caseId: item.caseId,
|
|
58834
|
+
// The name it had when it ran, which is what the summary of that Run all says too.
|
|
58835
|
+
name: item.name,
|
|
58836
|
+
outcome: item.outcome,
|
|
58837
|
+
attempts: item.runIds.length,
|
|
58838
|
+
...testCase ? { appId: testCase.appId } : {},
|
|
58839
|
+
...testCase?.reference ? { reference: testCase.reference } : {},
|
|
58840
|
+
...run2 ? {
|
|
58841
|
+
run: {
|
|
58842
|
+
runId: run2.runId,
|
|
58843
|
+
startedAt: run2.startedAt,
|
|
58844
|
+
passed: run2.passed,
|
|
58845
|
+
...run2.error ? { error: run2.error } : {},
|
|
58846
|
+
...!run2.passed && run2.failureScreenshot ? { failureScreenshot: run2.failureScreenshot } : {}
|
|
58847
|
+
}
|
|
58848
|
+
} : {},
|
|
58849
|
+
...summaryFile && summaryBytes ? { record: recordOf(state, summaryFile, summaryBytes) } : {},
|
|
58850
|
+
steps: testCase && run2 ? evidenceSteps(testCase, run2, names) : [],
|
|
58851
|
+
...item.failedStep ? { failedStep: item.failedStep } : {},
|
|
58852
|
+
...error ? { error } : {},
|
|
58853
|
+
...item.note ? { note: item.note } : {},
|
|
58854
|
+
...testCase?.editedAt && run2 && run2.startedAt < testCase.editedAt ? { stale: true } : {},
|
|
58855
|
+
...testCase ? {} : { missing: true }
|
|
58856
|
+
});
|
|
58857
|
+
}
|
|
58858
|
+
const image = await imageMap(
|
|
58859
|
+
state,
|
|
58860
|
+
cases.flatMap((item) => [...item.steps.map((step) => evidenceScreen(step, screens, item.run?.failureScreenshot)), item.run?.failureScreenshot]),
|
|
58861
|
+
SCREEN_WIDTH
|
|
58862
|
+
);
|
|
58863
|
+
const html = evidenceHtml({
|
|
58864
|
+
suite,
|
|
58865
|
+
cases,
|
|
58866
|
+
image,
|
|
58867
|
+
screens,
|
|
58868
|
+
record: recordOf(state, suiteFile, suiteBytes),
|
|
58869
|
+
...release2 ? { release: release2 } : {},
|
|
58870
|
+
...testedBy ? { testedBy } : {},
|
|
58871
|
+
...signoffRole ? { signoffRole } : {},
|
|
58872
|
+
...knownIssues ? { knownIssues } : {},
|
|
58873
|
+
...conclusion ? { conclusion } : {}
|
|
58874
|
+
});
|
|
58875
|
+
const dir = state.project.exportsDir();
|
|
58876
|
+
await mkdir10(dir, { recursive: true });
|
|
58877
|
+
await writeFile7(join11(dir, `evidence-${suiteId}.html`), html, "utf8");
|
|
58878
|
+
console.log(`[studio] wrote exports/evidence-${suiteId}.html`);
|
|
58879
|
+
return reply.type("text/html; charset=utf-8").header("cache-control", "no-store").send(html);
|
|
58880
|
+
});
|
|
58881
|
+
}
|
|
58882
|
+
|
|
57721
58883
|
// ../studio/src/server/exports.ts
|
|
57722
|
-
import { mkdir as
|
|
57723
|
-
import { join as
|
|
58884
|
+
import { mkdir as mkdir11, readFile as readFile10, writeFile as writeFile8 } from "node:fs/promises";
|
|
58885
|
+
import { join as join12 } from "node:path";
|
|
57724
58886
|
var CONTENT_TYPE = {
|
|
57725
58887
|
md: "text/markdown; charset=utf-8",
|
|
57726
58888
|
html: "text/html; charset=utf-8",
|
|
@@ -57747,7 +58909,7 @@ async function screenshotDataUrls(state, docs) {
|
|
|
57747
58909
|
for (const step of doc.steps) {
|
|
57748
58910
|
if (!step.screenshot || map.has(step.screenshot)) continue;
|
|
57749
58911
|
try {
|
|
57750
|
-
const bytes = await
|
|
58912
|
+
const bytes = await readFile10(state.project.path(step.screenshot));
|
|
57751
58913
|
map.set(step.screenshot, `data:image/png;base64,${bytes.toString("base64")}`);
|
|
57752
58914
|
} catch {
|
|
57753
58915
|
}
|
|
@@ -57767,8 +58929,8 @@ async function render(state, docs, format3, title) {
|
|
|
57767
58929
|
}
|
|
57768
58930
|
async function sendExport(state, reply, fileName, format3, content) {
|
|
57769
58931
|
const dir = state.project.exportsDir();
|
|
57770
|
-
await
|
|
57771
|
-
await
|
|
58932
|
+
await mkdir11(dir, { recursive: true });
|
|
58933
|
+
await writeFile8(join12(dir, fileName), content);
|
|
57772
58934
|
console.log(`[studio] wrote exports/${fileName}`);
|
|
57773
58935
|
return reply.type(CONTENT_TYPE[format3]).header("content-disposition", `attachment; filename="${fileName}"`).header("cache-control", "no-store").send(content);
|
|
57774
58936
|
}
|
|
@@ -57795,8 +58957,8 @@ function registerExportRoutes(app, state) {
|
|
|
57795
58957
|
|
|
57796
58958
|
// ../studio/src/server/files.ts
|
|
57797
58959
|
import { createReadStream as createReadStream3 } from "node:fs";
|
|
57798
|
-
import { open, readdir as
|
|
57799
|
-
import { basename as basename2, extname, posix as posix4, relative as
|
|
58960
|
+
import { open, readdir as readdir7, realpath as realpath2, stat as stat2 } from "node:fs/promises";
|
|
58961
|
+
import { basename as basename2, extname, posix as posix4, relative as relative3, resolve as resolve3, sep as sep4 } from "node:path";
|
|
57800
58962
|
var MAX_TEXT_BYTES = 512 * 1024;
|
|
57801
58963
|
var HIDDEN_NAMES = /* @__PURE__ */ new Set([".git", "node_modules"]);
|
|
57802
58964
|
var IMAGE_TYPES = {
|
|
@@ -57811,7 +58973,7 @@ function isHidden(name) {
|
|
|
57811
58973
|
return lower.startsWith(".env") || HIDDEN_NAMES.has(lower);
|
|
57812
58974
|
}
|
|
57813
58975
|
function isInside2(path, dir) {
|
|
57814
|
-
return path === dir || path.startsWith(dir +
|
|
58976
|
+
return path === dir || path.startsWith(dir + sep4);
|
|
57815
58977
|
}
|
|
57816
58978
|
async function rootFolders(state) {
|
|
57817
58979
|
const { dir } = state.project;
|
|
@@ -57826,10 +58988,10 @@ function readPath(query) {
|
|
|
57826
58988
|
return path;
|
|
57827
58989
|
}
|
|
57828
58990
|
async function resolveEntry(state, path) {
|
|
57829
|
-
const real = await realpath2(
|
|
58991
|
+
const real = await realpath2(resolve3(state.project.dir, path)).catch(() => void 0);
|
|
57830
58992
|
const roots = await Promise.all((await rootFolders(state)).map((folder) => realpath2(folder)));
|
|
57831
58993
|
const root = real === void 0 ? void 0 : roots.find((folder) => isInside2(real, folder));
|
|
57832
|
-
if (real === void 0 || root === void 0 ||
|
|
58994
|
+
if (real === void 0 || root === void 0 || relative3(root, real).split(sep4).some(isHidden)) {
|
|
57833
58995
|
throw new HttpError(404, "NOT_FOUND", "That is not a file in this project.");
|
|
57834
58996
|
}
|
|
57835
58997
|
return real;
|
|
@@ -57862,7 +59024,7 @@ function registerFileRoutes(app, state) {
|
|
|
57862
59024
|
const path = readPath(request.query);
|
|
57863
59025
|
const folder = await resolveEntry(state, path);
|
|
57864
59026
|
if (!(await stat2(folder)).isDirectory()) throw new HttpError(400, "BAD_REQUEST", "That is a file, not a folder.");
|
|
57865
|
-
const items = await
|
|
59027
|
+
const items = await readdir7(folder, { withFileTypes: true });
|
|
57866
59028
|
const entries = items.filter((item) => (item.isDirectory() || item.isFile()) && !isHidden(item.name)).map((item) => ({ path: posix4.join(path, item.name), name: item.name, kind: item.isDirectory() ? "folder" : "file" })).sort(byKindThenName);
|
|
57867
59029
|
return { entries };
|
|
57868
59030
|
});
|
|
@@ -57896,6 +59058,13 @@ function readPoint(body) {
|
|
|
57896
59058
|
}
|
|
57897
59059
|
return { x, y };
|
|
57898
59060
|
}
|
|
59061
|
+
function readSeconds(body) {
|
|
59062
|
+
const { seconds } = body ?? {};
|
|
59063
|
+
if (typeof seconds !== "number" || !Number.isInteger(seconds) || seconds < 1 || seconds > MAX_WAIT_SECONDS) {
|
|
59064
|
+
throw new HttpError(400, "BAD_REQUEST", `A wait is 1 to ${MAX_WAIT_SECONDS} seconds.`);
|
|
59065
|
+
}
|
|
59066
|
+
return seconds;
|
|
59067
|
+
}
|
|
57899
59068
|
function readString(body, key) {
|
|
57900
59069
|
const value = body ?? {};
|
|
57901
59070
|
if (typeof value[key] !== "string" || !value[key].trim()) {
|
|
@@ -57972,20 +59141,12 @@ async function replayCaseNow(state, testCase, steps, env) {
|
|
|
57972
59141
|
const described = describeDeviceError(error);
|
|
57973
59142
|
throw new HttpError(502, described.code, redactValues(`The case ${testCase.id} failed: ${described.message}`, env));
|
|
57974
59143
|
} finally {
|
|
57975
|
-
await
|
|
59144
|
+
await rm9(scratchPath, { force: true });
|
|
57976
59145
|
}
|
|
57977
59146
|
}
|
|
57978
|
-
function readPicks(body) {
|
|
57979
|
-
const picks = (body ?? {}).picks;
|
|
57980
|
-
if (picks === void 0) return [];
|
|
57981
|
-
if (!Array.isArray(picks) || !picks.every((pick) => Number.isInteger(pick) && pick >= 0)) {
|
|
57982
|
-
throw new HttpError(400, "BAD_PICK", 'Expected "picks" to be a list of candidate ids.');
|
|
57983
|
-
}
|
|
57984
|
-
return [...new Set(picks)];
|
|
57985
|
-
}
|
|
57986
59147
|
function readLink(body) {
|
|
57987
59148
|
const url = readString(body, "url").trim();
|
|
57988
|
-
if (
|
|
59149
|
+
if (!isOpenableLink(url)) {
|
|
57989
59150
|
throw new HttpError(400, "BAD_LINK", "Expected a link such as https://example.com/claims or myapp://claims/123.");
|
|
57990
59151
|
}
|
|
57991
59152
|
return url;
|
|
@@ -58030,6 +59191,7 @@ async function projectInfo(state) {
|
|
|
58030
59191
|
appId: settings.appId,
|
|
58031
59192
|
platform: settings.platform,
|
|
58032
59193
|
runVideo: settings.runVideo === true,
|
|
59194
|
+
evidencePack: settings.evidencePack === true,
|
|
58033
59195
|
videoHasSteps: state.maestro.findFfmpeg() !== void 0,
|
|
58034
59196
|
...settings.lastDeviceId ? { lastDeviceId: settings.lastDeviceId } : {},
|
|
58035
59197
|
blocks: blocks.map((block) => {
|
|
@@ -58051,7 +59213,8 @@ async function projectInfo(state) {
|
|
|
58051
59213
|
...clears ? { clearsAppData: true } : {},
|
|
58052
59214
|
...block.runsWith ? { runsWith: block.runsWith } : {},
|
|
58053
59215
|
...outline && outline.maestroOnly.length > 0 ? { maestroOnly: outline.maestroOnly } : {},
|
|
58054
|
-
...block.error ? { error: block.error } : {}
|
|
59216
|
+
...block.error ? { error: block.error } : {},
|
|
59217
|
+
...outline?.ownFlow ? { editable: true } : {}
|
|
58055
59218
|
};
|
|
58056
59219
|
}),
|
|
58057
59220
|
envKeys: envKeyList,
|
|
@@ -58061,14 +59224,33 @@ async function projectInfo(state) {
|
|
|
58061
59224
|
}
|
|
58062
59225
|
async function freshLatest(state) {
|
|
58063
59226
|
let latest = state.latest;
|
|
59227
|
+
if (latest?.hidden) return latest;
|
|
58064
59228
|
if (!latest || Date.now() - Date.parse(latest.takenAt) > STALE_SNAPSHOT_MS2) {
|
|
58065
59229
|
const fresh = await state.session.snapshot();
|
|
59230
|
+
if (!hasAppElements(fresh.nodes) || await phoneLocked(state)) return captureScratch(state);
|
|
58066
59231
|
latest = { ...fresh, screenshotPath: latest?.screenshotPath ?? scratchScreenshot(state) };
|
|
58067
59232
|
state.latest = latest;
|
|
58068
59233
|
}
|
|
58069
59234
|
return latest;
|
|
58070
59235
|
}
|
|
59236
|
+
async function refuseHiddenScreen(state) {
|
|
59237
|
+
if (!state.latest?.hidden && !await phoneLocked(state)) return;
|
|
59238
|
+
const now = await captureScratch(state);
|
|
59239
|
+
throw new HttpError(
|
|
59240
|
+
409,
|
|
59241
|
+
"SCREEN_HIDDEN",
|
|
59242
|
+
now.hidden ? "The phone's screen is off or locked, so nothing was pressed or recorded. Wake it and unlock it, then try again." : "The phone's screen is back. Nothing was pressed or recorded: have a look at it here, then try again."
|
|
59243
|
+
);
|
|
59244
|
+
}
|
|
59245
|
+
async function typeUnsavedPassword(state, text) {
|
|
59246
|
+
try {
|
|
59247
|
+
await state.session.typeText(text);
|
|
59248
|
+
} catch {
|
|
59249
|
+
throw new HttpError(502, "TYPE_FAILED", "Could not type into the password field. Check the phone, then try again.");
|
|
59250
|
+
}
|
|
59251
|
+
}
|
|
58071
59252
|
async function recordedAction(state, act, record, kind, text) {
|
|
59253
|
+
if (kind !== "pressKey") await refuseHiddenScreen(state);
|
|
58072
59254
|
await act();
|
|
58073
59255
|
let screenshotPath = scratchScreenshot(state);
|
|
58074
59256
|
let step;
|
|
@@ -58119,6 +59301,12 @@ function registerRoutes(app, state) {
|
|
|
58119
59301
|
await state.project.updateSettings({ runVideo: on });
|
|
58120
59302
|
return projectInfo(state);
|
|
58121
59303
|
});
|
|
59304
|
+
app.put("/api/project/evidence-pack", async (request) => {
|
|
59305
|
+
const on = request.body?.on;
|
|
59306
|
+
if (typeof on !== "boolean") throw new HttpError(400, "BAD_REQUEST", "Say whether to offer the evidence pack.");
|
|
59307
|
+
await state.project.updateSettings({ evidencePack: on });
|
|
59308
|
+
return projectInfo(state);
|
|
59309
|
+
});
|
|
58122
59310
|
app.get("/api/devices", async () => state.session.listDevices());
|
|
58123
59311
|
app.get("/api/session", async () => ({
|
|
58124
59312
|
open: state.session.isOpen,
|
|
@@ -58163,6 +59351,17 @@ function registerRoutes(app, state) {
|
|
|
58163
59351
|
requireSession(state);
|
|
58164
59352
|
return enqueue(state, async () => toScreenState(await captureScratch(state)));
|
|
58165
59353
|
});
|
|
59354
|
+
app.post("/api/screen/pick", async (request) => {
|
|
59355
|
+
const point = readPoint(request.body);
|
|
59356
|
+
requireSession(state);
|
|
59357
|
+
return enqueue(state, async () => {
|
|
59358
|
+
await refuseHiddenScreen(state);
|
|
59359
|
+
const latest = await freshLatest(state);
|
|
59360
|
+
const node = resolveTapTarget(latest.nodes, point, latest.viewport);
|
|
59361
|
+
const selector = chooseSelector(node, latest.nodes, point, latest.viewport);
|
|
59362
|
+
return { selector, password: node?.password === true, ...node ? { resolved: toLiteNode(node) } : {} };
|
|
59363
|
+
});
|
|
59364
|
+
});
|
|
58166
59365
|
app.get("/api/screen/image", async (_request, reply) => {
|
|
58167
59366
|
if (!state.latest || !await exists3(state.latest.screenshotPath)) {
|
|
58168
59367
|
throw new HttpError(404, "NO_SCREEN", "Nothing has been captured yet.");
|
|
@@ -58193,22 +59392,26 @@ function registerRoutes(app, state) {
|
|
|
58193
59392
|
return enqueue(state, async () => {
|
|
58194
59393
|
const recorder = state.recorder;
|
|
58195
59394
|
let recordedText = text;
|
|
59395
|
+
let unsaved = false;
|
|
58196
59396
|
if (recorder?.active) {
|
|
58197
59397
|
const env = await projectEnv(state);
|
|
58198
59398
|
const key = Object.keys(env).sort().find((candidate) => env[candidate] === text);
|
|
58199
59399
|
if (key) {
|
|
58200
59400
|
recordedText = `\${${key}}`;
|
|
58201
59401
|
} else if (state.latest && focusedPasswordField(state.latest.nodes)) {
|
|
58202
|
-
|
|
59402
|
+
if (state.recordingBlock) {
|
|
59403
|
+
recordedText = reference(passwordSetting(Object.keys(env)));
|
|
59404
|
+
unsaved = true;
|
|
59405
|
+
} else throw new HttpError(
|
|
58203
59406
|
400,
|
|
58204
59407
|
"PASSWORD_FIELD",
|
|
58205
|
-
`This is a password field. Put the password in ${
|
|
59408
|
+
`This is a password field. Put the password in ${join13(state.project.dir, ".env")} (for example PASSWORD=...) and type exactly that value; the case then records \${PASSWORD} instead of the secret.`
|
|
58206
59409
|
);
|
|
58207
59410
|
}
|
|
58208
59411
|
}
|
|
58209
59412
|
return recordedAction(
|
|
58210
59413
|
state,
|
|
58211
|
-
() => state.session.typeText(text),
|
|
59414
|
+
() => unsaved ? typeUnsavedPassword(state, text) : state.session.typeText(text),
|
|
58212
59415
|
(active, screenshot) => active.recordInput(recordedText, screenshot),
|
|
58213
59416
|
"type",
|
|
58214
59417
|
recordedText
|
|
@@ -58359,6 +59562,7 @@ function registerRoutes(app, state) {
|
|
|
58359
59562
|
const block = await prepareBlockUse(state, requested, env);
|
|
58360
59563
|
const { use } = block;
|
|
58361
59564
|
return enqueue(state, async () => {
|
|
59565
|
+
await refuseHiddenScreen(state);
|
|
58362
59566
|
if (block.runsWithMaestro) {
|
|
58363
59567
|
const testCase = state.recorder.toCase();
|
|
58364
59568
|
const step = { index: 1, kind: "block", file: use.file, ...use.values ? { values: use.values } : {}, at: nowIso() };
|
|
@@ -58379,6 +59583,30 @@ function registerRoutes(app, state) {
|
|
|
58379
59583
|
);
|
|
58380
59584
|
});
|
|
58381
59585
|
});
|
|
59586
|
+
app.post("/api/blocks/try", async (request) => {
|
|
59587
|
+
const requested = readBlockUse(request.body);
|
|
59588
|
+
requireSession(state);
|
|
59589
|
+
const env = await projectEnv(state);
|
|
59590
|
+
const block = await prepareBlockUse(state, requested, env);
|
|
59591
|
+
const { use } = block;
|
|
59592
|
+
return enqueue(state, async () => {
|
|
59593
|
+
requireNoRecording(state);
|
|
59594
|
+
await refuseHiddenScreen(state);
|
|
59595
|
+
if (block.runsWithMaestro) {
|
|
59596
|
+
const step = { index: 1, kind: "block", file: use.file, ...use.values ? { values: use.values } : {}, at: nowIso() };
|
|
59597
|
+
const appId = state.session.appId ?? (await state.project.readSettings()).appId;
|
|
59598
|
+
await runPrefixWithMaestro(state, {
|
|
59599
|
+
caseId: "try-block",
|
|
59600
|
+
partial: { id: "try-block", name: use.file, appId, deviceId: state.session.deviceId ?? "", createdAt: nowIso(), preconditionBlocks: [], steps: [step], expectations: [] },
|
|
59601
|
+
skipLaunch: true,
|
|
59602
|
+
what: `Block ${use.file}`
|
|
59603
|
+
});
|
|
59604
|
+
} else {
|
|
59605
|
+
await runBlockNow(state, block, env, `Block ${use.file}`);
|
|
59606
|
+
}
|
|
59607
|
+
return toScreenState(await captureScratch(state));
|
|
59608
|
+
});
|
|
59609
|
+
});
|
|
58382
59610
|
app.post("/api/project/test-users", async (request) => {
|
|
58383
59611
|
const { name, values } = request.body ?? {};
|
|
58384
59612
|
if (typeof name !== "string" || !isValues(values) || Object.keys(values).length === 0) {
|
|
@@ -58399,6 +59627,18 @@ function registerRoutes(app, state) {
|
|
|
58399
59627
|
}
|
|
58400
59628
|
return projectInfo(state);
|
|
58401
59629
|
});
|
|
59630
|
+
app.post("/api/project/saved-values", async (request) => {
|
|
59631
|
+
const { values } = request.body ?? {};
|
|
59632
|
+
if (!isValues(values) || Object.keys(values).length === 0 || Object.values(values).some((value) => !value)) {
|
|
59633
|
+
throw new HttpError(400, "BAD_SAVED_VALUE", 'Expected { values } with a value for each setting, such as { "EMAIL": "..." }.');
|
|
59634
|
+
}
|
|
59635
|
+
try {
|
|
59636
|
+
await state.project.addEnvValues(values);
|
|
59637
|
+
} catch (error) {
|
|
59638
|
+
throw new HttpError(409, "BAD_SAVED_VALUE", error instanceof Error ? error.message : String(error));
|
|
59639
|
+
}
|
|
59640
|
+
return projectInfo(state);
|
|
59641
|
+
});
|
|
58402
59642
|
app.post("/api/case/start", async (request) => {
|
|
58403
59643
|
const name = readString(request.body, "name").trim();
|
|
58404
59644
|
const blocks = readBlocks(request.body);
|
|
@@ -58426,6 +59666,7 @@ function registerRoutes(app, state) {
|
|
|
58426
59666
|
const preconditionBlocks = prepared.map((block) => block.use);
|
|
58427
59667
|
const withMaestro = await replayNeedsMaestro(state, { preconditionBlocks, steps: [], ...startFrom ? { startFrom } : {} });
|
|
58428
59668
|
return enqueue(state, async () => {
|
|
59669
|
+
requireNoRecording(state);
|
|
58429
59670
|
const deviceId = state.session.deviceId;
|
|
58430
59671
|
const appId = state.session.appId;
|
|
58431
59672
|
const caseId = await state.project.allocateCaseId(name);
|
|
@@ -58465,6 +59706,15 @@ function registerRoutes(app, state) {
|
|
|
58465
59706
|
return { caseId, screen };
|
|
58466
59707
|
});
|
|
58467
59708
|
});
|
|
59709
|
+
app.post("/api/case/wait", async (request) => {
|
|
59710
|
+
const seconds = readSeconds(request.body);
|
|
59711
|
+
requireSession(state);
|
|
59712
|
+
requireCase(state);
|
|
59713
|
+
return enqueue(
|
|
59714
|
+
state,
|
|
59715
|
+
() => recordedAction(state, async () => void 0, (recorder, screenshot) => recorder.recordWait(seconds, screenshot), "wait")
|
|
59716
|
+
);
|
|
59717
|
+
});
|
|
58468
59718
|
app.post("/api/case/expect", async (request) => {
|
|
58469
59719
|
const point = readPoint(request.body);
|
|
58470
59720
|
requireSession(state);
|
|
@@ -58478,8 +59728,8 @@ function registerRoutes(app, state) {
|
|
|
58478
59728
|
const evidence = state.project.evidencePath(recorder.caseId, recorder.evidenceNumberForNext("expect"));
|
|
58479
59729
|
let screenshot;
|
|
58480
59730
|
if (await exists3(latest.screenshotPath)) {
|
|
58481
|
-
await
|
|
58482
|
-
await
|
|
59731
|
+
await mkdir12(dirname9(evidence.absolute), { recursive: true });
|
|
59732
|
+
await copyFile3(latest.screenshotPath, evidence.absolute);
|
|
58483
59733
|
screenshot = evidence.relative;
|
|
58484
59734
|
}
|
|
58485
59735
|
const step = recorder.recordExpect(node, point, latest.viewport, screenshot, latest.nodes);
|
|
@@ -58491,6 +59741,7 @@ function registerRoutes(app, state) {
|
|
|
58491
59741
|
requireSession(state);
|
|
58492
59742
|
return enqueue(state, async () => {
|
|
58493
59743
|
const latest = await freshLatest(state);
|
|
59744
|
+
state.offered = latest;
|
|
58494
59745
|
const candidates = expectationCandidates(latest.nodes, latest.viewport);
|
|
58495
59746
|
const previous = state.editing?.mode === "rerecord" ? state.editing.originalExpectations : [];
|
|
58496
59747
|
const preselected = candidates.filter((candidate) => previous.some((expectation) => sameSelector(expectation.selector, candidate.selector))).map((candidate) => candidate.id);
|
|
@@ -58500,16 +59751,13 @@ function registerRoutes(app, state) {
|
|
|
58500
59751
|
app.post("/api/case/finish", async (request) => {
|
|
58501
59752
|
const picks = readPicks(request.body);
|
|
58502
59753
|
const recorder = requireCase(state);
|
|
59754
|
+
if (state.recordingBlock) throw new HttpError(409, "RECORDING_BLOCK", "This recording is for a block. Finish it from the Record tab.");
|
|
58503
59755
|
await stopPhoneCapture(state);
|
|
58504
59756
|
return enqueue(state, async () => {
|
|
59757
|
+
requireRecording(state, recorder);
|
|
58505
59758
|
const editing = state.editing;
|
|
58506
59759
|
if (editing?.mode !== "insert") {
|
|
58507
|
-
const
|
|
58508
|
-
const chosen = picks.map((pick) => {
|
|
58509
|
-
const candidate = candidates[pick];
|
|
58510
|
-
if (!candidate) throw new HttpError(400, "BAD_PICK", `There is no candidate ${pick} on the current screen.`);
|
|
58511
|
-
return candidate;
|
|
58512
|
-
});
|
|
59760
|
+
const chosen = pickedCandidates(state, picks);
|
|
58513
59761
|
if (editing) recorder.clearExpectations();
|
|
58514
59762
|
for (const candidate of chosen) recorder.addExpectation({ selector: candidate.selector, node: candidate.node });
|
|
58515
59763
|
}
|
|
@@ -58521,12 +59769,13 @@ function registerRoutes(app, state) {
|
|
|
58521
59769
|
const original = await state.project.readCase(editing.caseId).catch(() => void 0);
|
|
58522
59770
|
const kept = new Set(testCase.steps.map((step) => step.screenshot));
|
|
58523
59771
|
for (const step of original?.steps ?? []) {
|
|
58524
|
-
if (step.screenshot && !kept.has(step.screenshot)) await
|
|
59772
|
+
if (step.screenshot && !kept.has(step.screenshot)) await rm9(state.project.path(step.screenshot), { force: true });
|
|
58525
59773
|
}
|
|
58526
59774
|
}
|
|
58527
59775
|
const saved = await saveCaseAndFlow(state, testCase);
|
|
58528
59776
|
state.recorder = void 0;
|
|
58529
59777
|
state.editing = void 0;
|
|
59778
|
+
state.offered = void 0;
|
|
58530
59779
|
return {
|
|
58531
59780
|
caseId: testCase.id,
|
|
58532
59781
|
flowPath: saved.flowPath,
|
|
@@ -58539,19 +59788,21 @@ function registerRoutes(app, state) {
|
|
|
58539
59788
|
const recorder = requireCase(state);
|
|
58540
59789
|
await stopPhoneCapture(state);
|
|
58541
59790
|
return enqueue(state, async () => {
|
|
59791
|
+
requireRecording(state, recorder);
|
|
58542
59792
|
const caseId = recorder.caseId;
|
|
58543
59793
|
const editing = state.editing;
|
|
58544
59794
|
state.recorder = void 0;
|
|
58545
59795
|
state.editing = void 0;
|
|
59796
|
+
state.recordingBlock = void 0;
|
|
58546
59797
|
if (editing) {
|
|
58547
59798
|
const dir = state.project.evidenceDir(caseId);
|
|
58548
|
-
for (const name of await
|
|
59799
|
+
for (const name of await readdir8(dir).catch(() => [])) {
|
|
58549
59800
|
const number = evidenceNumber(name);
|
|
58550
|
-
if (number !== void 0 && number >= editing.firstNewEvidence) await
|
|
59801
|
+
if (number !== void 0 && number >= editing.firstNewEvidence) await rm9(join13(dir, name), { force: true });
|
|
58551
59802
|
}
|
|
58552
59803
|
return { ok: true, caseId, restored: true };
|
|
58553
59804
|
}
|
|
58554
|
-
await
|
|
59805
|
+
await rm9(state.project.evidenceDir(caseId), { recursive: true, force: true });
|
|
58555
59806
|
return { ok: true, caseId };
|
|
58556
59807
|
});
|
|
58557
59808
|
});
|
|
@@ -58566,19 +59817,21 @@ function registerRoutes(app, state) {
|
|
|
58566
59817
|
return sendPng(reply, path);
|
|
58567
59818
|
});
|
|
58568
59819
|
registerCaseRoutes(app, state);
|
|
59820
|
+
registerBlockRoutes(app, state);
|
|
58569
59821
|
registerReplayRoutes(app, state);
|
|
58570
59822
|
registerExportRoutes(app, state);
|
|
59823
|
+
registerEvidenceRoutes(app, state);
|
|
58571
59824
|
registerFileRoutes(app, state);
|
|
58572
59825
|
registerPhoneRoutes(app, state);
|
|
58573
59826
|
}
|
|
58574
59827
|
|
|
58575
59828
|
// ../studio/src/server/run-video.ts
|
|
58576
|
-
import { execFile as
|
|
58577
|
-
import { existsSync as
|
|
58578
|
-
import { mkdtemp as mkdtemp3, readFile as
|
|
59829
|
+
import { execFile as execFile6, spawn as spawn4 } from "node:child_process";
|
|
59830
|
+
import { existsSync as existsSync5 } from "node:fs";
|
|
59831
|
+
import { mkdtemp as mkdtemp3, readFile as readFile11, rename as rename5, rm as rm10, writeFile as writeFile9 } from "node:fs/promises";
|
|
58579
59832
|
import { createRequire } from "node:module";
|
|
58580
59833
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
58581
|
-
import { delimiter as delimiter2, dirname as
|
|
59834
|
+
import { delimiter as delimiter2, dirname as dirname10, join as join14 } from "node:path";
|
|
58582
59835
|
import { fileURLToPath } from "node:url";
|
|
58583
59836
|
import { promisify as promisify4 } from "node:util";
|
|
58584
59837
|
|
|
@@ -59168,10 +60421,10 @@ function ffmpegCandidates(platform, env) {
|
|
|
59168
60421
|
const name = platform === "win32" ? "ffmpeg.exe" : "ffmpeg";
|
|
59169
60422
|
const dirs = (env.PATH ?? "").split(delimiter2).filter(Boolean);
|
|
59170
60423
|
if (platform === "darwin") dirs.push("/opt/homebrew/bin", "/usr/local/bin");
|
|
59171
|
-
return [...env.ARIADNE_FFMPEG ? [env.ARIADNE_FFMPEG] : [], ...dirs.map((dir) =>
|
|
60424
|
+
return [...env.ARIADNE_FFMPEG ? [env.ARIADNE_FFMPEG] : [], ...dirs.map((dir) => join14(dir, name))];
|
|
59172
60425
|
}
|
|
59173
60426
|
function findFfmpeg() {
|
|
59174
|
-
return ffmpegCandidates(process.platform, process.env).find((candidate) =>
|
|
60427
|
+
return ffmpegCandidates(process.platform, process.env).find((candidate) => existsSync5(candidate));
|
|
59175
60428
|
}
|
|
59176
60429
|
function parseVideoInfo(output) {
|
|
59177
60430
|
const size = /Video:.*?\b(\d{2,5})x(\d{2,5})\b/.exec(output);
|
|
@@ -59184,7 +60437,7 @@ function parseVideoInfo(output) {
|
|
|
59184
60437
|
};
|
|
59185
60438
|
}
|
|
59186
60439
|
async function videoInfo(ffmpeg, file) {
|
|
59187
|
-
const output = await promisify4(
|
|
60440
|
+
const output = await promisify4(execFile6)(ffmpeg, ["-hide_banner", "-i", file]).then(
|
|
59188
60441
|
(result) => result.stderr,
|
|
59189
60442
|
(error) => error.stderr ?? ""
|
|
59190
60443
|
);
|
|
@@ -59193,9 +60446,9 @@ async function videoInfo(ffmpeg, file) {
|
|
|
59193
60446
|
var started;
|
|
59194
60447
|
function startRasteriser(assetsDir) {
|
|
59195
60448
|
if (!started) {
|
|
59196
|
-
const bundled =
|
|
59197
|
-
const wasm2 =
|
|
59198
|
-
started =
|
|
60449
|
+
const bundled = join14(assetsDir, "index_bg.wasm");
|
|
60450
|
+
const wasm2 = existsSync5(bundled) ? bundled : createRequire(import.meta.url).resolve("@resvg/resvg-wasm/index_bg.wasm");
|
|
60451
|
+
started = readFile11(wasm2).then((bytes) => initWasm(bytes));
|
|
59199
60452
|
}
|
|
59200
60453
|
return started;
|
|
59201
60454
|
}
|
|
@@ -59208,7 +60461,7 @@ function pngOf(svg, fonts) {
|
|
|
59208
60461
|
return png;
|
|
59209
60462
|
}
|
|
59210
60463
|
function runFfmpeg(ffmpeg, args) {
|
|
59211
|
-
return new Promise((
|
|
60464
|
+
return new Promise((resolve7, reject) => {
|
|
59212
60465
|
const child = spawn4(ffmpeg, args, { stdio: ["ignore", "ignore", "pipe"], windowsHide: true });
|
|
59213
60466
|
let errors = "";
|
|
59214
60467
|
child.stderr.on("data", (chunk2) => {
|
|
@@ -59221,7 +60474,7 @@ function runFfmpeg(ffmpeg, args) {
|
|
|
59221
60474
|
});
|
|
59222
60475
|
child.on("close", (code) => {
|
|
59223
60476
|
clearTimeout(timer);
|
|
59224
|
-
if (code === 0)
|
|
60477
|
+
if (code === 0) resolve7();
|
|
59225
60478
|
else reject(new Error(errors.trim() || `ffmpeg exited with code ${code}`));
|
|
59226
60479
|
});
|
|
59227
60480
|
});
|
|
@@ -59234,25 +60487,25 @@ async function frameRunVideo(input) {
|
|
|
59234
60487
|
const clock = runVideoClock(input.commands, info.durationMs);
|
|
59235
60488
|
if (!clock) throw new Error("Maestro reported no start for the phone video");
|
|
59236
60489
|
await startRasteriser(assetsDir);
|
|
59237
|
-
const fonts = await Promise.all(["NotoSansMono-Regular.ttf", "NotoSansMono-Bold.ttf"].map((name) =>
|
|
60490
|
+
const fonts = await Promise.all(["NotoSansMono-Regular.ttf", "NotoSansMono-Bold.ttf"].map((name) => readFile11(join14(assetsDir, name))));
|
|
59238
60491
|
const phone = runVideoPhoneBox(info);
|
|
59239
60492
|
const states = runVideoStates(rows, clock, info.durationMs);
|
|
59240
|
-
const work = await mkdtemp3(
|
|
60493
|
+
const work = await mkdtemp3(join14(tmpdir3(), "ariadne-run-video-"));
|
|
59241
60494
|
try {
|
|
59242
60495
|
const lines = ["ffconcat version 1.0"];
|
|
59243
60496
|
const totalMs = info.durationMs + RUN_VIDEO_HOLD_MS;
|
|
59244
60497
|
for (const [index, state] of states.entries()) {
|
|
59245
60498
|
const name = `state-${String(index).padStart(4, "0")}.png`;
|
|
59246
60499
|
const svg = runVideoFrameSvg({ caseName: input.caseName, device: input.device, appId: input.appId, passed: input.passed, rows, state, phone });
|
|
59247
|
-
await
|
|
60500
|
+
await writeFile9(join14(work, name), pngOf(svg, fonts));
|
|
59248
60501
|
const untilMs = states[index + 1]?.fromMs ?? totalMs;
|
|
59249
60502
|
lines.push(`file '${name}'`, `duration ${((untilMs - state.fromMs) / 1e3).toFixed(3)}`);
|
|
59250
60503
|
if (index === states.length - 1) lines.push(`file '${name}'`);
|
|
59251
60504
|
}
|
|
59252
|
-
await
|
|
60505
|
+
await writeFile9(join14(work, "states.ffconcat"), `${lines.join("\n")}
|
|
59253
60506
|
`);
|
|
59254
|
-
await
|
|
59255
|
-
const output =
|
|
60507
|
+
await writeFile9(join14(work, "corners.png"), pngOf(runVideoCornersSvg(phone), fonts));
|
|
60508
|
+
const output = join14(dirname10(video), "video.framed.mp4");
|
|
59256
60509
|
const graph = [
|
|
59257
60510
|
`[1:v]fps=30,tpad=stop_mode=clone:stop_duration=${RUN_VIDEO_HOLD_MS / 1e3},scale=${phone.width}:${phone.height}:flags=lanczos[phone]`,
|
|
59258
60511
|
`[0:v]fps=30,format=yuv420p[frame]`,
|
|
@@ -59269,11 +60522,11 @@ async function frameRunVideo(input) {
|
|
|
59269
60522
|
"-safe",
|
|
59270
60523
|
"0",
|
|
59271
60524
|
"-i",
|
|
59272
|
-
|
|
60525
|
+
join14(work, "states.ffconcat"),
|
|
59273
60526
|
"-i",
|
|
59274
60527
|
video,
|
|
59275
60528
|
"-i",
|
|
59276
|
-
|
|
60529
|
+
join14(work, "corners.png"),
|
|
59277
60530
|
"-filter_complex",
|
|
59278
60531
|
graph,
|
|
59279
60532
|
"-map",
|
|
@@ -59289,9 +60542,9 @@ async function frameRunVideo(input) {
|
|
|
59289
60542
|
"+faststart",
|
|
59290
60543
|
output
|
|
59291
60544
|
]);
|
|
59292
|
-
await
|
|
60545
|
+
await rename5(output, video);
|
|
59293
60546
|
} finally {
|
|
59294
|
-
await
|
|
60547
|
+
await rm10(work, { recursive: true, force: true });
|
|
59295
60548
|
}
|
|
59296
60549
|
}
|
|
59297
60550
|
|
|
@@ -59320,7 +60573,7 @@ async function buildServer(options) {
|
|
|
59320
60573
|
return reply.status(403).send({ error: { code: "NOT_THIS_COMPUTER", message: "This request did not come from Ariadne on this computer." } });
|
|
59321
60574
|
});
|
|
59322
60575
|
app.addHook("onResponse", async (request, reply) => {
|
|
59323
|
-
if (request.url.startsWith("/api/screen/image") || request.url.startsWith("/api/evidence/")) return;
|
|
60576
|
+
if (request.url.startsWith("/api/screen/image") || request.url.startsWith("/api/screen/refresh") || request.url.startsWith("/api/evidence/")) return;
|
|
59324
60577
|
if (request.url.startsWith("/api/runs/")) return;
|
|
59325
60578
|
if (!request.url.startsWith("/api/")) return;
|
|
59326
60579
|
console.log(
|
|
@@ -59332,7 +60585,7 @@ async function buildServer(options) {
|
|
|
59332
60585
|
registerSetupRoutes(app, setup);
|
|
59333
60586
|
registerDiagnosticsRoutes(app, state, { appVersion: options.appVersion ?? "development", setup });
|
|
59334
60587
|
const uiDir = options.uiDir ?? uiDist;
|
|
59335
|
-
if (
|
|
60588
|
+
if (existsSync6(uiDir)) {
|
|
59336
60589
|
await app.register(import_static.default, { root: uiDir, prefix: "/" });
|
|
59337
60590
|
app.setNotFoundHandler((request, reply) => {
|
|
59338
60591
|
if (request.url.startsWith("/api/")) {
|
|
@@ -59344,7 +60597,7 @@ async function buildServer(options) {
|
|
|
59344
60597
|
app.addHook("onClose", async () => {
|
|
59345
60598
|
await stopPhoneCapture(state).catch(() => void 0);
|
|
59346
60599
|
if (state.session.isOpen) await state.session.close().catch(() => void 0);
|
|
59347
|
-
await
|
|
60600
|
+
await rm11(state.scratchDir, { recursive: true, force: true }).catch(() => void 0);
|
|
59348
60601
|
});
|
|
59349
60602
|
return app;
|
|
59350
60603
|
}
|
|
@@ -59369,8 +60622,8 @@ async function startStudio(options) {
|
|
|
59369
60622
|
app,
|
|
59370
60623
|
url: studioUrl(host, port),
|
|
59371
60624
|
port,
|
|
59372
|
-
projectDir:
|
|
59373
|
-
servesUi:
|
|
60625
|
+
projectDir: resolve4(options.projectDir),
|
|
60626
|
+
servesUi: existsSync6(options.uiDir ?? uiDist),
|
|
59374
60627
|
close: async () => {
|
|
59375
60628
|
await app.close();
|
|
59376
60629
|
log.stop();
|
|
@@ -59379,8 +60632,8 @@ async function startStudio(options) {
|
|
|
59379
60632
|
}
|
|
59380
60633
|
|
|
59381
60634
|
// src/args.ts
|
|
59382
|
-
import { existsSync as
|
|
59383
|
-
import { join as
|
|
60635
|
+
import { existsSync as existsSync7 } from "node:fs";
|
|
60636
|
+
import { join as join15, resolve as resolve5 } from "node:path";
|
|
59384
60637
|
import { parseArgs } from "node:util";
|
|
59385
60638
|
var DEFAULT_PORT2 = 4711;
|
|
59386
60639
|
var DEFAULT_HOST = "127.0.0.1";
|
|
@@ -59417,16 +60670,16 @@ point at Maestro and the Android SDK when they are not in the usual places.`;
|
|
|
59417
60670
|
var UsageError = class extends Error {
|
|
59418
60671
|
};
|
|
59419
60672
|
function isProjectDir(dir) {
|
|
59420
|
-
return
|
|
60673
|
+
return existsSync7(join15(dir, "project.json")) && existsSync7(join15(dir, "cases"));
|
|
59421
60674
|
}
|
|
59422
60675
|
function projectDirIn(dir) {
|
|
59423
|
-
const inner =
|
|
59424
|
-
return !
|
|
60676
|
+
const inner = join15(dir, ARIADNE_DIR);
|
|
60677
|
+
return !existsSync7(join15(dir, "project.json")) && existsSync7(join15(inner, "project.json")) ? inner : dir;
|
|
59425
60678
|
}
|
|
59426
60679
|
function defaultProjectDir(cwd) {
|
|
59427
|
-
if (isProjectDir(cwd)) return
|
|
59428
|
-
const inner = projectDirIn(
|
|
59429
|
-
return inner !==
|
|
60680
|
+
if (isProjectDir(cwd)) return resolve5(cwd);
|
|
60681
|
+
const inner = projectDirIn(resolve5(cwd));
|
|
60682
|
+
return inner !== resolve5(cwd) ? inner : resolve5(cwd, DEFAULT_PROJECT_FOLDER);
|
|
59430
60683
|
}
|
|
59431
60684
|
function parsePort(value, source) {
|
|
59432
60685
|
const port = Number(value);
|
|
@@ -59459,13 +60712,13 @@ function parseCliArgs(argv, env, cwd) {
|
|
|
59459
60712
|
if (values.version) return { kind: "version" };
|
|
59460
60713
|
if (positionals[0] === "init") {
|
|
59461
60714
|
if (positionals.length > 2) throw new UsageError(`Expected at most one folder after init, got ${positionals.length - 1}: ${positionals.slice(1).join(" ")}`);
|
|
59462
|
-
return { kind: "init", folder:
|
|
60715
|
+
return { kind: "init", folder: resolve5(cwd, positionals[1] ?? ".") };
|
|
59463
60716
|
}
|
|
59464
60717
|
if (positionals.length > 1) {
|
|
59465
60718
|
throw new UsageError(`Expected at most one project folder, got ${positionals.length}: ${positionals.join(" ")}`);
|
|
59466
60719
|
}
|
|
59467
60720
|
const folder = positionals[0] ?? env.ARIADNE_PROJECT_DIR;
|
|
59468
|
-
const projectDir = folder ? projectDirIn(
|
|
60721
|
+
const projectDir = folder ? projectDirIn(resolve5(cwd, folder)) : defaultProjectDir(cwd);
|
|
59469
60722
|
const port = values.port !== void 0 ? parsePort(values.port, "--port") : env.PORT ? parsePort(env.PORT, "PORT") : DEFAULT_PORT2;
|
|
59470
60723
|
const host = values.host?.trim() || env.HOST?.trim() || DEFAULT_HOST;
|
|
59471
60724
|
const sessionName = values.session?.trim() || env.ARIADNE_SESSION_NAME?.trim() || DEFAULT_SESSION;
|
|
@@ -59473,8 +60726,8 @@ function parseCliArgs(argv, env, cwd) {
|
|
|
59473
60726
|
}
|
|
59474
60727
|
|
|
59475
60728
|
// src/init.ts
|
|
59476
|
-
import { existsSync as
|
|
59477
|
-
import { join as
|
|
60729
|
+
import { existsSync as existsSync8, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
60730
|
+
import { join as join16, relative as relative4, sep as sep5 } from "node:path";
|
|
59478
60731
|
var RECORDED_DIR = "recorded";
|
|
59479
60732
|
var BLOCK_FOLDER_NAMES = ["subflows", "shared", "common", "helpers", "utils"];
|
|
59480
60733
|
var CONFIG_KEYS = /^(flows|includeTags|excludeTags|executionOrder|testOutputDir|disableRetries|platform|notifications)\s*:/m;
|
|
@@ -59489,7 +60742,7 @@ function isDirectory(path) {
|
|
|
59489
60742
|
}
|
|
59490
60743
|
function hasMaestroConfig(dir) {
|
|
59491
60744
|
for (const name of ["config.yaml", "config.yml"]) {
|
|
59492
|
-
const file =
|
|
60745
|
+
const file = join16(dir, name);
|
|
59493
60746
|
try {
|
|
59494
60747
|
if (CONFIG_KEYS.test(readFileSync(file, "utf8"))) return true;
|
|
59495
60748
|
} catch {
|
|
@@ -59498,7 +60751,7 @@ function hasMaestroConfig(dir) {
|
|
|
59498
60751
|
return false;
|
|
59499
60752
|
}
|
|
59500
60753
|
function findMaestroWorkspace(folder) {
|
|
59501
|
-
const dotMaestro =
|
|
60754
|
+
const dotMaestro = join16(folder, ".maestro");
|
|
59502
60755
|
if (isDirectory(dotMaestro)) return dotMaestro;
|
|
59503
60756
|
if (hasMaestroConfig(folder)) return folder;
|
|
59504
60757
|
let entries = [];
|
|
@@ -59509,7 +60762,7 @@ function findMaestroWorkspace(folder) {
|
|
|
59509
60762
|
}
|
|
59510
60763
|
for (const name of entries) {
|
|
59511
60764
|
if (name === ARIADNE_DIR || SKIPPED_DIRS.has(name)) continue;
|
|
59512
|
-
const dir =
|
|
60765
|
+
const dir = join16(folder, name);
|
|
59513
60766
|
if (isDirectory(dir) && hasMaestroConfig(dir)) return dir;
|
|
59514
60767
|
}
|
|
59515
60768
|
return void 0;
|
|
@@ -59524,7 +60777,7 @@ function flowFiles(dir, depth = 0) {
|
|
|
59524
60777
|
}
|
|
59525
60778
|
const found = [];
|
|
59526
60779
|
for (const name of entries) {
|
|
59527
|
-
const path =
|
|
60780
|
+
const path = join16(dir, name);
|
|
59528
60781
|
if (isDirectory(path)) {
|
|
59529
60782
|
if (!SKIPPED_DIRS.has(name) && name !== ARIADNE_DIR) found.push(...flowFiles(path, depth + 1));
|
|
59530
60783
|
} else if (/\.ya?ml$/i.test(name) && !(depth === 0 && /^config\.ya?ml$/i.test(name))) {
|
|
@@ -59554,7 +60807,7 @@ function commonAppId(workspace) {
|
|
|
59554
60807
|
function blockFolders2(workspace) {
|
|
59555
60808
|
let entries = [];
|
|
59556
60809
|
try {
|
|
59557
|
-
entries = readdirSync(workspace).filter((name) => isDirectory(
|
|
60810
|
+
entries = readdirSync(workspace).filter((name) => isDirectory(join16(workspace, name)));
|
|
59558
60811
|
} catch {
|
|
59559
60812
|
return [];
|
|
59560
60813
|
}
|
|
@@ -59563,19 +60816,19 @@ function blockFolders2(workspace) {
|
|
|
59563
60816
|
var InitError = class extends Error {
|
|
59564
60817
|
};
|
|
59565
60818
|
function toPosix(path) {
|
|
59566
|
-
return path.split(
|
|
60819
|
+
return path.split(sep5).join("/");
|
|
59567
60820
|
}
|
|
59568
60821
|
function initProject(folder) {
|
|
59569
|
-
const projectDir =
|
|
59570
|
-
const projectFile =
|
|
59571
|
-
if (
|
|
60822
|
+
const projectDir = join16(folder, ARIADNE_DIR);
|
|
60823
|
+
const projectFile = join16(projectDir, "project.json");
|
|
60824
|
+
if (existsSync8(projectFile)) {
|
|
59572
60825
|
throw new InitError(`${projectFile} already exists; this folder already has an Ariadne project. Nothing was changed.`);
|
|
59573
60826
|
}
|
|
59574
60827
|
const created = [];
|
|
59575
60828
|
const makeDir = (dir) => {
|
|
59576
60829
|
if (isDirectory(dir)) return;
|
|
59577
60830
|
mkdirSync(dir, { recursive: true });
|
|
59578
|
-
created.push(dir +
|
|
60831
|
+
created.push(dir + sep5);
|
|
59579
60832
|
};
|
|
59580
60833
|
const settings = { appId: "", platform: "android" };
|
|
59581
60834
|
const workspace = findMaestroWorkspace(folder);
|
|
@@ -59584,7 +60837,7 @@ function initProject(folder) {
|
|
|
59584
60837
|
const found = commonAppId(workspace);
|
|
59585
60838
|
flowCount = found.flowCount;
|
|
59586
60839
|
if (found.appId) settings.appId = found.appId;
|
|
59587
|
-
const fromProject = toPosix(
|
|
60840
|
+
const fromProject = toPosix(relative4(projectDir, workspace));
|
|
59588
60841
|
const blocks = blockFolders2(workspace).map((name) => `${fromProject}/${name}`);
|
|
59589
60842
|
settings.paths = { ...blocks.length > 0 ? { blocks } : {}, flows: `${fromProject}/${RECORDED_DIR}` };
|
|
59590
60843
|
}
|
|
@@ -59592,18 +60845,18 @@ function initProject(folder) {
|
|
|
59592
60845
|
writeFileSync(projectFile, `${JSON.stringify(settings, null, 2)}
|
|
59593
60846
|
`, "utf8");
|
|
59594
60847
|
created.push(projectFile);
|
|
59595
|
-
makeDir(
|
|
59596
|
-
const gitignore =
|
|
59597
|
-
if (!
|
|
60848
|
+
makeDir(join16(projectDir, "cases"));
|
|
60849
|
+
const gitignore = join16(projectDir, ".gitignore");
|
|
60850
|
+
if (!existsSync8(gitignore)) {
|
|
59598
60851
|
writeFileSync(gitignore, PROJECT_GITIGNORE, "utf8");
|
|
59599
60852
|
created.push(gitignore);
|
|
59600
60853
|
}
|
|
59601
|
-
if (workspace) makeDir(
|
|
60854
|
+
if (workspace) makeDir(join16(workspace, RECORDED_DIR));
|
|
59602
60855
|
return { projectDir, settings, ...workspace ? { workspace } : {}, flowCount, created };
|
|
59603
60856
|
}
|
|
59604
60857
|
function initReport(result, folder, cwd) {
|
|
59605
60858
|
const shown = (path) => {
|
|
59606
|
-
const rel =
|
|
60859
|
+
const rel = relative4(cwd, path);
|
|
59607
60860
|
return rel.startsWith("..") ? path.replace(/[\\/]+$/, "") : toPosix(rel) || ".";
|
|
59608
60861
|
};
|
|
59609
60862
|
const lines = [];
|
|
@@ -59614,14 +60867,14 @@ function initReport(result, folder, cwd) {
|
|
|
59614
60867
|
lines.push("No Maestro workspace found (no .maestro folder or Maestro config.yaml), so blocks and flows stay inside the project.");
|
|
59615
60868
|
}
|
|
59616
60869
|
lines.push("");
|
|
59617
|
-
for (const path of result.created) lines.push(` created ${shown(path)}${path.endsWith(
|
|
60870
|
+
for (const path of result.created) lines.push(` created ${shown(path)}${path.endsWith(sep5) ? "/" : ""}`);
|
|
59618
60871
|
lines.push("");
|
|
59619
60872
|
const { paths } = result.settings;
|
|
59620
60873
|
lines.push(` appId: ${result.settings.appId || "(not set; enter it in the studio)"}`);
|
|
59621
60874
|
if (paths?.flows) lines.push(` flows: ${paths.flows} (recorded cases are written here)`);
|
|
59622
60875
|
lines.push(` blocks: ${paths?.blocks?.join(", ") ?? (paths ? '(none found; add folders to "paths.blocks" in project.json)' : "blocks/")}`);
|
|
59623
60876
|
lines.push("");
|
|
59624
|
-
const target =
|
|
60877
|
+
const target = relative4(cwd, folder) && shown(folder);
|
|
59625
60878
|
lines.push(`Next: ariadne-studio${target ? ` ${/^[\w./:\\-]+$/.test(target) ? target : `"${target}"`}` : ""}`);
|
|
59626
60879
|
return lines;
|
|
59627
60880
|
}
|
|
@@ -59655,13 +60908,9 @@ function version() {
|
|
|
59655
60908
|
}
|
|
59656
60909
|
}
|
|
59657
60910
|
function addSdkToPath() {
|
|
59658
|
-
const
|
|
59659
|
-
if (!home) return;
|
|
59660
|
-
const platformTools = join15(home, "platform-tools");
|
|
60911
|
+
const platformTools = join17(androidHome() ?? defaultSdkDir(), "platform-tools");
|
|
59661
60912
|
const dirs = (process.env.PATH ?? "").split(delimiter3).filter(Boolean);
|
|
59662
|
-
if (
|
|
59663
|
-
process.env.PATH = [...dirs, platformTools].join(delimiter3);
|
|
59664
|
-
}
|
|
60913
|
+
if (!dirs.includes(platformTools)) process.env.PATH = [...dirs, platformTools].join(delimiter3);
|
|
59665
60914
|
}
|
|
59666
60915
|
function notifyParent(message) {
|
|
59667
60916
|
process.send?.(message);
|
|
@@ -59685,7 +60934,7 @@ function shellQuote(path) {
|
|
|
59685
60934
|
async function explainPortInUse(options) {
|
|
59686
60935
|
const url = studioUrl(options.host, options.port);
|
|
59687
60936
|
const otherProject = await runningStudioAt(url);
|
|
59688
|
-
if (otherProject &&
|
|
60937
|
+
if (otherProject && resolve6(otherProject) === options.projectDir) {
|
|
59689
60938
|
console.log(`Ariadne Studio is already running for this project at ${url}`);
|
|
59690
60939
|
if (options.open) openBrowser(url);
|
|
59691
60940
|
return 0;
|
|
@@ -59707,8 +60956,8 @@ async function run(options) {
|
|
|
59707
60956
|
port: options.port,
|
|
59708
60957
|
host: options.host,
|
|
59709
60958
|
sessionName: options.sessionName,
|
|
59710
|
-
...
|
|
59711
|
-
...
|
|
60959
|
+
...existsSync9(bundledUi) ? { uiDir: bundledUi } : {},
|
|
60960
|
+
...existsSync9(bundledVideoAssets) ? { videoAssetsDir: bundledVideoAssets } : {}
|
|
59712
60961
|
});
|
|
59713
60962
|
} catch (error) {
|
|
59714
60963
|
if (isAddressInUse(error)) {
|