@percy/cli-app 1.32.2-beta.0 → 1.32.3-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/exec.js +168 -1
- package/dist/index.js +1 -1
- package/package.json +4 -4
package/dist/exec.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import os from 'os';
|
|
3
|
+
import path from 'path';
|
|
1
4
|
import command from '@percy/cli-command';
|
|
2
5
|
import * as ExecPlugin from '@percy/cli-exec';
|
|
3
6
|
export const ping = ExecPlugin.ping;
|
|
@@ -11,6 +14,158 @@ export const start = command('start', {
|
|
|
11
14
|
skipDiscovery: true
|
|
12
15
|
}
|
|
13
16
|
}, ExecPlugin.start.callback);
|
|
17
|
+
|
|
18
|
+
// Locate the `test` subcommand in argv. Maestro accepts global parent
|
|
19
|
+
// flags before the subcommand, e.g.:
|
|
20
|
+
// maestro test flow.yaml
|
|
21
|
+
// maestro --udid <serial> test flow.yaml
|
|
22
|
+
// maestro --platform=android test flow.yaml
|
|
23
|
+
// maestro --verbose --no-ansi test flow.yaml
|
|
24
|
+
// We must find `test` by scanning rather than checking args[1] === 'test',
|
|
25
|
+
// or our injects silently no-op when the customer pins a device.
|
|
26
|
+
//
|
|
27
|
+
// Returns the index of the `test` literal, or -1 if not present. Skips
|
|
28
|
+
// over the value of known value-taking parent flags so a literal `test`
|
|
29
|
+
// supplied as a flag value (e.g. `--udid test`) isn't mistaken for the
|
|
30
|
+
// subcommand. Equals-form (`--flag=value`) doesn't need a skip — the
|
|
31
|
+
// value is part of the same argv token.
|
|
32
|
+
const MAESTRO_PARENT_VALUE_FLAGS = new Set(['--udid', '--device', '-p', '--platform', '--host', '--port', '--driver-host-port']);
|
|
33
|
+
function findTestSubcommandIdx(args) {
|
|
34
|
+
for (let i = 1; i < args.length; i++) {
|
|
35
|
+
const tok = args[i];
|
|
36
|
+
if (tok === 'test') return i;
|
|
37
|
+
if (typeof tok === 'string' && MAESTRO_PARENT_VALUE_FLAGS.has(tok)) {
|
|
38
|
+
i++; // skip the value of this flag
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return -1;
|
|
42
|
+
}
|
|
43
|
+
function hasExistingPercyServerFlag(args, testIdx) {
|
|
44
|
+
for (let i = testIdx + 1; i < args.length - 1; i++) {
|
|
45
|
+
if (args[i] === '-e' && /^PERCY_SERVER=/.test(args[i + 1])) return true;
|
|
46
|
+
}
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Returns the customer-supplied value of `--test-output-dir` (whether the
|
|
51
|
+
// space-form `--test-output-dir <path>` or the equals-form
|
|
52
|
+
// `--test-output-dir=<path>` — both are valid picocli syntax), or null if
|
|
53
|
+
// absent. Returning the value (not just an index) lets the screenshot-dir
|
|
54
|
+
// helper align PERCY_MAESTRO_SCREENSHOT_DIR with the customer's value in
|
|
55
|
+
// either form without re-deriving it.
|
|
56
|
+
//
|
|
57
|
+
// An empty value (space-form value === '' or equals-form `--test-output-dir=`
|
|
58
|
+
// with nothing after the `=`) is treated as ABSENT — returning the empty
|
|
59
|
+
// string would tell the caller "customer set this" and bypass the
|
|
60
|
+
// auto-resolve fallback, leaving Maestro to default its output location
|
|
61
|
+
// while PERCY_MAESTRO_SCREENSHOT_DIR stays unset. That mismatch silently
|
|
62
|
+
// produces all-404 snapshots, so fall through to the auto-resolve path
|
|
63
|
+
// instead.
|
|
64
|
+
const TEST_OUTPUT_DIR_EQ_PREFIX = '--test-output-dir=';
|
|
65
|
+
function findTestOutputDirValue(args, testIdx) {
|
|
66
|
+
for (let i = testIdx + 1; i < args.length; i++) {
|
|
67
|
+
const tok = args[i];
|
|
68
|
+
if (tok === '--test-output-dir' && i + 1 < args.length) {
|
|
69
|
+
const val = args[i + 1];
|
|
70
|
+
if (typeof val === 'string' && val.length > 0) return val;
|
|
71
|
+
} else if (typeof tok === 'string' && tok.startsWith(TEST_OUTPUT_DIR_EQ_PREFIX)) {
|
|
72
|
+
const val = tok.slice(TEST_OUTPUT_DIR_EQ_PREFIX.length);
|
|
73
|
+
if (val.length > 0) return val;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Maestro's GraalJS sandbox does NOT inherit the parent process's env,
|
|
80
|
+
// so `PERCY_SERVER_ADDRESS` exported by app:exec is invisible to the
|
|
81
|
+
// SDK. When wrapping `maestro test`, surface the CLI address through
|
|
82
|
+
// Maestro's only env channel — `-e KEY=VALUE` flags — so the SDK
|
|
83
|
+
// healthcheck can find the local CLI without the customer having to
|
|
84
|
+
// pair ports manually. No-op when the customer already supplied their
|
|
85
|
+
// own `-e PERCY_SERVER=...`.
|
|
86
|
+
//
|
|
87
|
+
// When percy?.address() is falsy (percy disabled, start failed), emit a
|
|
88
|
+
// WARN so the customer is not surprised by a silent zero-snapshot build.
|
|
89
|
+
// The customer-override skip case (their own `-e PERCY_SERVER=...` is in
|
|
90
|
+
// argv) does NOT warn — that's intentional flow control, not a problem.
|
|
91
|
+
export function maybeInjectMaestroServer(ctx, log) {
|
|
92
|
+
var _ctx$percy;
|
|
93
|
+
const args = ctx === null || ctx === void 0 ? void 0 : ctx.argv;
|
|
94
|
+
if (!Array.isArray(args) || args.length < 2) return;
|
|
95
|
+
if (path.basename(args[0]) !== 'maestro') return;
|
|
96
|
+
const testIdx = findTestSubcommandIdx(args);
|
|
97
|
+
if (testIdx < 0) return;
|
|
98
|
+
if (hasExistingPercyServerFlag(args, testIdx)) return;
|
|
99
|
+
const addr = (_ctx$percy = ctx.percy) === null || _ctx$percy === void 0 ? void 0 : _ctx$percy.address();
|
|
100
|
+
if (!addr) {
|
|
101
|
+
log === null || log === void 0 || log.warn('app:exec did not start the Percy CLI server (percy disabled or start ' + 'failed); -e PERCY_SERVER not injected into maestro test. Snapshots will ' + 'NOT be uploaded. Set PERCY_TOKEN and re-run, or check the percy log above.');
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
// Inject after `test` so `-e KEY=VAL` is bound to the `test` subcommand
|
|
105
|
+
// (the only Maestro subcommand that accepts `-e`).
|
|
106
|
+
args.splice(testIdx + 1, 0, '-e', `PERCY_SERVER=${addr}`);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Auto-resolve the Maestro screenshot output directory so customers don't
|
|
110
|
+
// have to pair `export PERCY_MAESTRO_SCREENSHOT_DIR=...` with a matching
|
|
111
|
+
// `--test-output-dir <same>` in their maestro test command.
|
|
112
|
+
//
|
|
113
|
+
// Resolution order:
|
|
114
|
+
// 1. Customer set BOTH process.env.PERCY_MAESTRO_SCREENSHOT_DIR and
|
|
115
|
+
// --test-output-dir in argv → trust them, do nothing.
|
|
116
|
+
// 2. Customer set PERCY_MAESTRO_SCREENSHOT_DIR only → use it, inject
|
|
117
|
+
// `--test-output-dir <env value>` into argv.
|
|
118
|
+
// 3. Customer set --test-output-dir only → use that value, mirror it
|
|
119
|
+
// into process.env.PERCY_MAESTRO_SCREENSHOT_DIR (so the SDK +
|
|
120
|
+
// CLI relay see the same path).
|
|
121
|
+
// 4. Neither set → pick `${process.cwd()}/.percy-out`. On any mkdir
|
|
122
|
+
// failure (read-only CWD, EACCES, EEXIST as a file), fall back to
|
|
123
|
+
// `${os.tmpdir()}/percy-maestro-<pid>` with a WARN log.
|
|
124
|
+
//
|
|
125
|
+
// The env-var update and argv splice always keep both sources of truth
|
|
126
|
+
// (SDK reads env var; Maestro reads the flag) aligned to the same path.
|
|
127
|
+
export function maybeInjectScreenshotDir(ctx, log) {
|
|
128
|
+
const args = ctx === null || ctx === void 0 ? void 0 : ctx.argv;
|
|
129
|
+
if (!Array.isArray(args) || args.length < 2) return;
|
|
130
|
+
if (path.basename(args[0]) !== 'maestro') return;
|
|
131
|
+
const testIdx = findTestSubcommandIdx(args);
|
|
132
|
+
if (testIdx < 0) return;
|
|
133
|
+
const envSet = !!process.env.PERCY_MAESTRO_SCREENSHOT_DIR;
|
|
134
|
+
const existingFlagValue = findTestOutputDirValue(args, testIdx);
|
|
135
|
+
const flagSet = existingFlagValue !== null;
|
|
136
|
+
|
|
137
|
+
// Fully customer-controlled — nothing to do.
|
|
138
|
+
if (envSet && flagSet) return;
|
|
139
|
+
let resolved;
|
|
140
|
+
if (envSet) {
|
|
141
|
+
resolved = process.env.PERCY_MAESTRO_SCREENSHOT_DIR;
|
|
142
|
+
} else if (flagSet) {
|
|
143
|
+
resolved = existingFlagValue;
|
|
144
|
+
} else {
|
|
145
|
+
const preferred = path.join(process.cwd(), '.percy-out');
|
|
146
|
+
try {
|
|
147
|
+
fs.mkdirSync(preferred, {
|
|
148
|
+
recursive: true
|
|
149
|
+
});
|
|
150
|
+
resolved = preferred;
|
|
151
|
+
} catch (err) {
|
|
152
|
+
const fallback = path.join(os.tmpdir(), `percy-maestro-${process.pid}`);
|
|
153
|
+
try {
|
|
154
|
+
fs.mkdirSync(fallback, {
|
|
155
|
+
recursive: true
|
|
156
|
+
});
|
|
157
|
+
} catch (_) {
|
|
158
|
+
// tmpdir mkdir failure is exceedingly rare; fall through and let
|
|
159
|
+
// downstream code surface a clearer error than this helper can.
|
|
160
|
+
}
|
|
161
|
+
resolved = fallback;
|
|
162
|
+
log === null || log === void 0 || log.warn(`Could not create ${preferred} (${err.code || err.message}); ` + `falling back to ${fallback}. Set PERCY_MAESTRO_SCREENSHOT_DIR to ` + 'pick a specific location.');
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
if (!envSet) process.env.PERCY_MAESTRO_SCREENSHOT_DIR = resolved;
|
|
166
|
+
// Inject right after `test` (the subcommand that owns `--test-output-dir`).
|
|
167
|
+
if (!flagSet) args.splice(testIdx + 1, 0, '--test-output-dir', resolved);
|
|
168
|
+
}
|
|
14
169
|
export const exec = command('exec', {
|
|
15
170
|
description: 'Start and stop Percy around a supplied command for native apps',
|
|
16
171
|
usage: '[options] -- <command>',
|
|
@@ -23,5 +178,17 @@ export const exec = command('exec', {
|
|
|
23
178
|
projectType: 'app',
|
|
24
179
|
skipDiscovery: true
|
|
25
180
|
}
|
|
26
|
-
},
|
|
181
|
+
}, async function* (ctx) {
|
|
182
|
+
// The two helpers splice their flag groups at argv index 2 (between `test`
|
|
183
|
+
// and the flow file) because `-e` and `--test-output-dir` are
|
|
184
|
+
// `test`-subcommand options. Resulting argv for `maestro test flow.yaml`:
|
|
185
|
+
// maestro test --test-output-dir <dir> -e PERCY_SERVER=<url> flow.yaml
|
|
186
|
+
// iOS driver port: not prescribed from this side — the @percy/core relay
|
|
187
|
+
// reads `PERCY_IOS_DRIVER_HOST_PORT` (BS-host-injected on production
|
|
188
|
+
// hosts) and probes the documented Maestro 2.4.0 single-simulator default
|
|
189
|
+
// (`127.0.0.1:7001`) when it isn't set. See `packages/core/src/maestro-hierarchy.js`.
|
|
190
|
+
maybeInjectMaestroServer(ctx, ctx.log);
|
|
191
|
+
maybeInjectScreenshotDir(ctx, ctx.log);
|
|
192
|
+
yield* ExecPlugin.default.callback(ctx);
|
|
193
|
+
});
|
|
27
194
|
export default exec;
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export { default, app } from './app.js';
|
|
2
|
-
export { exec, start, stop, ping } from './exec.js';
|
|
2
|
+
export { exec, start, stop, ping, maybeInjectMaestroServer, maybeInjectScreenshotDir } from './exec.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@percy/cli-app",
|
|
3
|
-
"version": "1.32.
|
|
3
|
+
"version": "1.32.3-beta.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -33,8 +33,8 @@
|
|
|
33
33
|
]
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@percy/cli-command": "1.32.
|
|
37
|
-
"@percy/cli-exec": "1.32.
|
|
36
|
+
"@percy/cli-command": "1.32.3-beta.0",
|
|
37
|
+
"@percy/cli-exec": "1.32.3-beta.0"
|
|
38
38
|
},
|
|
39
|
-
"gitHead": "
|
|
39
|
+
"gitHead": "e8af175695b4afdb78cdd48b67037710f5c8b1c9"
|
|
40
40
|
}
|