@percy/core 1.32.0-beta.2 → 1.32.0-beta.4
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/api.js +59 -4
- package/dist/config.js +7 -2
- package/package.json +9 -9
package/dist/api.js
CHANGED
|
@@ -16,6 +16,7 @@ import { Readable } from 'stream';
|
|
|
16
16
|
// This change ensures better compatibility and avoids relying on Node.js-specific APIs that might cause issues in ESM environments.
|
|
17
17
|
import { fileURLToPath } from 'url';
|
|
18
18
|
import { createRequire } from 'module';
|
|
19
|
+
import { configSchema } from './config.js';
|
|
19
20
|
export const getPercyDomPath = url => {
|
|
20
21
|
try {
|
|
21
22
|
return createRequire(url).resolve('@percy/dom');
|
|
@@ -35,6 +36,57 @@ function encodeURLSearchParams(subj, prefix) {
|
|
|
35
36
|
return typeof subj === 'object' ? Object.entries(subj).map(([key, value]) => encodeURLSearchParams(value, prefix ? `${prefix}[${key}]` : key)).join('&') : `${prefix}=${encodeURIComponent(subj)}`;
|
|
36
37
|
}
|
|
37
38
|
|
|
39
|
+
// Walks the config schema and collects dot-paths of any fields marked `httpReadOnly: true`
|
|
40
|
+
// that are present in `body`. Driving this from the schema means new HTTP-blocked fields
|
|
41
|
+
// only need a one-line annotation next to their definition — no list to keep in sync here.
|
|
42
|
+
function findHttpReadOnlyPaths(body, schema, path = '') {
|
|
43
|
+
if (!body || typeof body !== 'object' || !(schema !== null && schema !== void 0 && schema.properties)) return [];
|
|
44
|
+
let paths = [];
|
|
45
|
+
for (let [key, propSchema] of Object.entries(schema.properties)) {
|
|
46
|
+
if (!Object.prototype.hasOwnProperty.call(body, key)) continue;
|
|
47
|
+
let childPath = path ? `${path}.${key}` : key;
|
|
48
|
+
if (propSchema !== null && propSchema !== void 0 && propSchema.httpReadOnly) {
|
|
49
|
+
paths.push(childPath);
|
|
50
|
+
} else {
|
|
51
|
+
paths.push(...findHttpReadOnlyPaths(body[key], propSchema, childPath));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return paths;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Top-level configSchema is a map of subschemas keyed by top-level config namespace
|
|
58
|
+
// (`discovery`, `snapshot`, …). Wrap it as a single object schema so the walker can recurse
|
|
59
|
+
// uniformly from the root.
|
|
60
|
+
const ROOT_CONFIG_SCHEMA = {
|
|
61
|
+
type: 'object',
|
|
62
|
+
properties: configSchema
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
// Removes each dot-path's leaf from a deep clone of `body` and logs a warning per path.
|
|
66
|
+
// Returns the original `body` unchanged when `paths` is empty so we don't pay for a clone
|
|
67
|
+
// on every config request. Exported for unit testing: the `?.` chain in the reduce is a
|
|
68
|
+
// defensive guard for paths whose ancestor is absent from `body`. Through the production
|
|
69
|
+
// caller (stripBlockedConfigFields → findHttpReadOnlyPaths) every intermediate is verified
|
|
70
|
+
// present, so the guard is unreachable in normal use — but the explicit paths parameter
|
|
71
|
+
// lets a unit test exercise it without contorting the schema.
|
|
72
|
+
export function _applyHttpReadOnlyStripping(body, paths, log) {
|
|
73
|
+
if (!paths.length) return body;
|
|
74
|
+
let stripped = JSON.parse(JSON.stringify(body));
|
|
75
|
+
for (let p of paths) {
|
|
76
|
+
let parts = p.split('.');
|
|
77
|
+
let leaf = parts.pop();
|
|
78
|
+
let parent = parts.reduce((o, k) => o === null || o === void 0 ? void 0 : o[k], stripped);
|
|
79
|
+
if (parent && typeof parent === 'object') delete parent[leaf];
|
|
80
|
+
log.warn(`Ignoring \`${p}\` from /percy/config request: this field can only be set via the config file or CLI at startup.`);
|
|
81
|
+
}
|
|
82
|
+
return stripped;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Returns a body with `httpReadOnly` fields removed. Caller guarantees `body` is truthy.
|
|
86
|
+
function stripBlockedConfigFields(body, log) {
|
|
87
|
+
return _applyHttpReadOnlyStripping(body, findHttpReadOnlyPaths(body, ROOT_CONFIG_SCHEMA), log);
|
|
88
|
+
}
|
|
89
|
+
|
|
38
90
|
// Parse PNG IHDR chunk for the screenshot's actual rendered dimensions.
|
|
39
91
|
// Returns { width, height } when the buffer is a valid PNG with non-zero
|
|
40
92
|
// dimensions, or null otherwise (non-PNG signature, truncated file, zero
|
|
@@ -297,10 +349,13 @@ export function createPercyServer(percy, port) {
|
|
|
297
349
|
});
|
|
298
350
|
})
|
|
299
351
|
// get or set config options
|
|
300
|
-
.route(['get', 'post'], '/percy/config', async (req, res) =>
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
352
|
+
.route(['get', 'post'], '/percy/config', async (req, res) => {
|
|
353
|
+
let body = req.body && stripBlockedConfigFields(req.body, logger('core:server'));
|
|
354
|
+
return res.json(200, {
|
|
355
|
+
config: body ? percy.set(body) : percy.config,
|
|
356
|
+
success: true
|
|
357
|
+
});
|
|
358
|
+
})
|
|
304
359
|
// responds once idle (may take a long time)
|
|
305
360
|
.route('get', '/percy/idle', async (req, res) => res.json(200, {
|
|
306
361
|
success: await percy.idle().then(() => true)
|
package/dist/config.js
CHANGED
|
@@ -597,8 +597,12 @@ export const configSchema = {
|
|
|
597
597
|
type: 'object',
|
|
598
598
|
additionalProperties: false,
|
|
599
599
|
properties: {
|
|
600
|
+
// httpReadOnly: never settable over /percy/config — accepting these would let any
|
|
601
|
+
// local process execute arbitrary code as the Percy user via flags like
|
|
602
|
+
// --renderer-cmd-prefix / --gpu-launcher. Must be set via static config or CLI.
|
|
600
603
|
executable: {
|
|
601
|
-
type: 'string'
|
|
604
|
+
type: 'string',
|
|
605
|
+
httpReadOnly: true
|
|
602
606
|
},
|
|
603
607
|
timeout: {
|
|
604
608
|
type: 'integer'
|
|
@@ -607,7 +611,8 @@ export const configSchema = {
|
|
|
607
611
|
type: 'array',
|
|
608
612
|
items: {
|
|
609
613
|
type: 'string'
|
|
610
|
-
}
|
|
614
|
+
},
|
|
615
|
+
httpReadOnly: true
|
|
611
616
|
},
|
|
612
617
|
headless: {
|
|
613
618
|
type: 'boolean'
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@percy/core",
|
|
3
|
-
"version": "1.32.0-beta.
|
|
3
|
+
"version": "1.32.0-beta.4",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -46,12 +46,12 @@
|
|
|
46
46
|
"dependencies": {
|
|
47
47
|
"@grpc/grpc-js": "^1.14.3",
|
|
48
48
|
"@grpc/proto-loader": "^0.8.0",
|
|
49
|
-
"@percy/client": "1.32.0-beta.
|
|
50
|
-
"@percy/config": "1.32.0-beta.
|
|
51
|
-
"@percy/dom": "1.32.0-beta.
|
|
52
|
-
"@percy/logger": "1.32.0-beta.
|
|
53
|
-
"@percy/monitoring": "1.32.0-beta.
|
|
54
|
-
"@percy/webdriver-utils": "1.32.0-beta.
|
|
49
|
+
"@percy/client": "1.32.0-beta.4",
|
|
50
|
+
"@percy/config": "1.32.0-beta.4",
|
|
51
|
+
"@percy/dom": "1.32.0-beta.4",
|
|
52
|
+
"@percy/logger": "1.32.0-beta.4",
|
|
53
|
+
"@percy/monitoring": "1.32.0-beta.4",
|
|
54
|
+
"@percy/webdriver-utils": "1.32.0-beta.4",
|
|
55
55
|
"busboy": "^1.6.0",
|
|
56
56
|
"content-disposition": "^0.5.4",
|
|
57
57
|
"cross-spawn": "^7.0.3",
|
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
"yaml": "^2.4.1"
|
|
68
68
|
},
|
|
69
69
|
"optionalDependencies": {
|
|
70
|
-
"@percy/cli-doctor": "1.32.0-beta.
|
|
70
|
+
"@percy/cli-doctor": "1.32.0-beta.4"
|
|
71
71
|
},
|
|
72
|
-
"gitHead": "
|
|
72
|
+
"gitHead": "2e5b1654806b609f961890f1a49fbc4154da1de2"
|
|
73
73
|
}
|