@reshotdev/screenshot 0.0.1-beta.2 → 0.0.1-beta.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +138 -47
- package/package.json +27 -16
- package/src/commands/auth.js +159 -30
- package/src/commands/capture-dom.js +50 -0
- package/src/commands/certify.js +62 -0
- package/src/commands/compose.js +220 -0
- package/src/commands/doctor-release.js +74 -0
- package/src/commands/doctor-target.js +108 -0
- package/src/commands/drifts.js +16 -69
- package/src/commands/import-tests.js +13 -13
- package/src/commands/init.js +16 -277
- package/src/commands/publish.js +484 -257
- package/src/commands/pull.js +302 -35
- package/src/commands/refresh.js +166 -0
- package/src/commands/run.js +292 -12
- package/src/commands/setup-wizard.js +348 -496
- package/src/commands/status.js +334 -126
- package/src/commands/sync.js +28 -236
- package/src/commands/ui.js +1 -1
- package/src/commands/variation.js +194 -0
- package/src/commands/verify-publish.js +46 -0
- package/src/index.js +383 -118
- package/src/lib/api-client.js +172 -60
- package/src/lib/auto-update/refresh.js +598 -0
- package/src/lib/auto-update/scene-runtime.compose.tsx +73 -0
- package/src/lib/auto-update/spec.js +89 -0
- package/src/lib/capture-engine.js +179 -9
- package/src/lib/capture-script-runner.js +639 -214
- package/src/lib/certification.js +887 -0
- package/src/lib/compose-context.js +156 -0
- package/src/lib/compose-pack.js +42 -0
- package/src/lib/compose-runtime.js +34 -0
- package/src/lib/compose-upload.js +142 -0
- package/src/lib/config.js +186 -81
- package/src/lib/dom-capture.js +64 -0
- package/src/lib/ensure-browser.js +147 -0
- package/src/lib/output-path-template.js +3 -3
- package/src/lib/record-cdp.js +288 -16
- package/src/lib/record-clip.js +83 -3
- package/src/lib/record-config.js +1 -5
- package/src/lib/release-doctor.js +321 -0
- package/src/lib/resolve-targets.js +60 -0
- package/src/lib/run-manifest.js +148 -0
- package/src/lib/standalone-mode.js +1 -1
- package/src/lib/storage-providers.js +5 -5
- package/src/lib/style-engine.js +5 -5
- package/src/lib/target-contract.js +292 -0
- package/src/lib/ui-api-helpers.js +118 -0
- package/src/lib/ui-api.js +31 -824
- package/src/lib/ui-asset-cleanup.js +62 -0
- package/src/lib/ui-output-versions.js +165 -0
- package/src/lib/ui-recorder-routes.js +341 -0
- package/src/lib/ui-scenario-metadata.js +161 -0
- package/vendor/compose/dist/auto-update.cjs +5544 -0
- package/vendor/compose/dist/auto-update.mjs +5518 -0
- package/vendor/compose/dist/capture.cjs +1450 -0
- package/vendor/compose/dist/capture.mjs +1416 -0
- package/vendor/compose/dist/eligibility.cjs +5331 -0
- package/vendor/compose/dist/eligibility.mjs +5313 -0
- package/vendor/compose/dist/index.cjs +2046 -0
- package/vendor/compose/dist/index.mjs +1997 -0
- package/vendor/compose/dist/jsx-dev-runtime.cjs +55 -0
- package/vendor/compose/dist/jsx-dev-runtime.mjs +27 -0
- package/vendor/compose/dist/jsx-runtime.cjs +58 -0
- package/vendor/compose/dist/jsx-runtime.mjs +31 -0
- package/vendor/compose/dist/render.cjs +558 -0
- package/vendor/compose/dist/render.mjs +515 -0
- package/vendor/compose/dist/verify-cli.cjs +3806 -0
- package/vendor/compose/dist/verify-cli.mjs +3812 -0
- package/vendor/compose/dist/verify.cjs +3880 -0
- package/vendor/compose/dist/verify.mjs +3858 -0
- package/web/manager/dist/assets/index-D0S2otug.js +507 -0
- package/web/manager/dist/index.html +1 -1
- package/src/commands/ci-run.js +0 -123
- package/src/commands/ci-setup.js +0 -288
- package/src/commands/ingest.js +0 -458
- package/src/commands/setup.js +0 -137
- package/src/commands/validate-docs.js +0 -529
- package/src/lib/playwright-runner.js +0 -252
- package/web/manager/dist/assets/index--ZgioErz.js +0 -507
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const TARGET_TIERS = new Set(["certified", "candidate", "custom"]);
|
|
4
|
+
const TARGET_AUTH_MODES = new Set(["public", "fixture", "live-auth"]);
|
|
5
|
+
const SCENARIO_CAPTURE_CLASSES = new Set([
|
|
6
|
+
"public",
|
|
7
|
+
"fixture-auth",
|
|
8
|
+
"live-auth",
|
|
9
|
+
]);
|
|
10
|
+
const PUBLISH_POLICIES = new Set(["required", "optional"]);
|
|
11
|
+
|
|
12
|
+
function normalizeTargetTier(value) {
|
|
13
|
+
const normalized = String(value || "custom").trim().toLowerCase();
|
|
14
|
+
return TARGET_TIERS.has(normalized) ? normalized : "custom";
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function normalizeTargetAuthMode(value) {
|
|
18
|
+
const normalized = String(value || "public").trim().toLowerCase();
|
|
19
|
+
if (normalized === "fixture-auth") return "fixture";
|
|
20
|
+
if (normalized === "authenticated" || normalized === "auth") {
|
|
21
|
+
return "live-auth";
|
|
22
|
+
}
|
|
23
|
+
return TARGET_AUTH_MODES.has(normalized) ? normalized : "public";
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function normalizeScenarioCaptureClass(value, requiresAuth, defaultAuthMode) {
|
|
27
|
+
const normalized = String(value || "").trim().toLowerCase();
|
|
28
|
+
|
|
29
|
+
if (!normalized) {
|
|
30
|
+
if (requiresAuth) {
|
|
31
|
+
return defaultAuthMode === "fixture" ? "fixture-auth" : "live-auth";
|
|
32
|
+
}
|
|
33
|
+
return "public";
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (
|
|
37
|
+
normalized === "fixture" ||
|
|
38
|
+
normalized === "docs-fixture" ||
|
|
39
|
+
normalized === "fixture-auth"
|
|
40
|
+
) {
|
|
41
|
+
return "fixture-auth";
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (
|
|
45
|
+
normalized === "live-auth" ||
|
|
46
|
+
normalized === "auth" ||
|
|
47
|
+
normalized === "authenticated"
|
|
48
|
+
) {
|
|
49
|
+
return "live-auth";
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (
|
|
53
|
+
normalized === "public" ||
|
|
54
|
+
normalized === "public-docs" ||
|
|
55
|
+
normalized === "public-explorer"
|
|
56
|
+
) {
|
|
57
|
+
return "public";
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return requiresAuth
|
|
61
|
+
? defaultAuthMode === "fixture"
|
|
62
|
+
? "fixture-auth"
|
|
63
|
+
: "live-auth"
|
|
64
|
+
: "public";
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function normalizeReadyContract(scenario) {
|
|
68
|
+
const ready = scenario.ready && typeof scenario.ready === "object"
|
|
69
|
+
? scenario.ready
|
|
70
|
+
: {};
|
|
71
|
+
const waitForReady =
|
|
72
|
+
scenario.waitForReady && typeof scenario.waitForReady === "object"
|
|
73
|
+
? scenario.waitForReady
|
|
74
|
+
: {};
|
|
75
|
+
|
|
76
|
+
const selector =
|
|
77
|
+
ready.selector ||
|
|
78
|
+
scenario.readySelector ||
|
|
79
|
+
waitForReady.selector ||
|
|
80
|
+
null;
|
|
81
|
+
const expression =
|
|
82
|
+
ready.expression ||
|
|
83
|
+
scenario.readyExpression ||
|
|
84
|
+
waitForReady.expression ||
|
|
85
|
+
null;
|
|
86
|
+
const timeout =
|
|
87
|
+
ready.timeout ||
|
|
88
|
+
waitForReady.timeout ||
|
|
89
|
+
scenario.readyTimeout ||
|
|
90
|
+
null;
|
|
91
|
+
|
|
92
|
+
if (!selector && !expression && !timeout) {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
...(selector ? { selector } : {}),
|
|
98
|
+
...(expression ? { expression } : {}),
|
|
99
|
+
...(timeout ? { timeout } : {}),
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function normalizeStringArray(value) {
|
|
104
|
+
if (!Array.isArray(value)) {
|
|
105
|
+
return [];
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return value
|
|
109
|
+
.map((item) => String(item || "").trim())
|
|
110
|
+
.filter(Boolean);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function normalizeOneOrManyStrings(value) {
|
|
114
|
+
if (Array.isArray(value)) {
|
|
115
|
+
return normalizeStringArray(value);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const normalized = String(value || "").trim();
|
|
119
|
+
return normalized ? [normalized] : [];
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function inferExpectedArtifacts(scenario) {
|
|
123
|
+
return (scenario.steps || [])
|
|
124
|
+
.filter((step) => step.action === "screenshot" && step.key)
|
|
125
|
+
.map((step) => String(step.key));
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function normalizePublishPolicy(value) {
|
|
129
|
+
const normalized = String(value || "required").trim().toLowerCase();
|
|
130
|
+
return PUBLISH_POLICIES.has(normalized) ? normalized : "required";
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function normalizeScenarioContract(scenario, target) {
|
|
134
|
+
const defaultAuthMode = target?.defaultAuthMode || "public";
|
|
135
|
+
const captureClass = normalizeScenarioCaptureClass(
|
|
136
|
+
scenario.captureClass,
|
|
137
|
+
Boolean(scenario.requiresAuth),
|
|
138
|
+
defaultAuthMode,
|
|
139
|
+
);
|
|
140
|
+
const ready = normalizeReadyContract(scenario);
|
|
141
|
+
const requiredSelectors = normalizeStringArray(scenario.requiredSelectors);
|
|
142
|
+
const readySelector = ready?.selector || null;
|
|
143
|
+
const expectedArtifacts = normalizeStringArray(scenario.expectedArtifacts);
|
|
144
|
+
|
|
145
|
+
return {
|
|
146
|
+
...scenario,
|
|
147
|
+
captureClass,
|
|
148
|
+
requiresAuth: captureClass !== "public",
|
|
149
|
+
ready,
|
|
150
|
+
readySelector: readySelector || undefined,
|
|
151
|
+
readyExpression: ready?.expression || undefined,
|
|
152
|
+
readyTimeout: ready?.timeout || scenario.readyTimeout,
|
|
153
|
+
waitForReady: ready || scenario.waitForReady || null,
|
|
154
|
+
requiredRoutes: normalizeStringArray(
|
|
155
|
+
scenario.requiredRoutes && scenario.requiredRoutes.length > 0
|
|
156
|
+
? scenario.requiredRoutes
|
|
157
|
+
: scenario.url
|
|
158
|
+
? [scenario.url]
|
|
159
|
+
: [],
|
|
160
|
+
),
|
|
161
|
+
requiredSelectors:
|
|
162
|
+
requiredSelectors.length > 0
|
|
163
|
+
? requiredSelectors
|
|
164
|
+
: readySelector
|
|
165
|
+
? [readySelector]
|
|
166
|
+
: [],
|
|
167
|
+
needsWorkspaceInjection:
|
|
168
|
+
scenario.needsWorkspaceInjection !== undefined
|
|
169
|
+
? Boolean(scenario.needsWorkspaceInjection)
|
|
170
|
+
: captureClass !== "public",
|
|
171
|
+
expectedArtifacts:
|
|
172
|
+
expectedArtifacts.length > 0 ? expectedArtifacts : inferExpectedArtifacts(scenario),
|
|
173
|
+
publishPolicy: normalizePublishPolicy(scenario.publishPolicy),
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function normalizeTargetBlock(config) {
|
|
178
|
+
const raw = config.target && typeof config.target === "object"
|
|
179
|
+
? config.target
|
|
180
|
+
: {};
|
|
181
|
+
|
|
182
|
+
const fixture =
|
|
183
|
+
raw.fixture && typeof raw.fixture === "object" ? raw.fixture : {};
|
|
184
|
+
const authPreflightUrls = normalizeOneOrManyStrings(
|
|
185
|
+
raw.authPreflightUrls || raw.authPreflightUrl,
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
return {
|
|
189
|
+
key: String(raw.key || config.name || config.projectId || "local-target"),
|
|
190
|
+
displayName: String(raw.displayName || raw.key || config.name || "Local Target"),
|
|
191
|
+
tier: normalizeTargetTier(raw.tier),
|
|
192
|
+
owner: String(raw.owner || "local"),
|
|
193
|
+
baseUrl: String(raw.baseUrl || config.baseUrl || "").replace(/\/+$/, ""),
|
|
194
|
+
captureSafe: Boolean(raw.captureSafe),
|
|
195
|
+
defaultAuthMode: normalizeTargetAuthMode(raw.defaultAuthMode),
|
|
196
|
+
supportedLocalCommand: String(
|
|
197
|
+
raw.supportedLocalCommand ||
|
|
198
|
+
raw.localServerCommand ||
|
|
199
|
+
raw.startCommand ||
|
|
200
|
+
"",
|
|
201
|
+
).trim() || null,
|
|
202
|
+
fixture:
|
|
203
|
+
fixture.command || fixture.script || fixture.healthUrl
|
|
204
|
+
? {
|
|
205
|
+
...(fixture.command ? { command: String(fixture.command) } : {}),
|
|
206
|
+
...(fixture.script ? { script: String(fixture.script) } : {}),
|
|
207
|
+
...(fixture.healthUrl ? { healthUrl: String(fixture.healthUrl) } : {}),
|
|
208
|
+
}
|
|
209
|
+
: null,
|
|
210
|
+
requiredEnv: normalizeStringArray(raw.requiredEnv),
|
|
211
|
+
certificationScenarioKeys: normalizeStringArray(
|
|
212
|
+
raw.certificationScenarioKeys || raw.certifiedScenarios || [],
|
|
213
|
+
),
|
|
214
|
+
authPreflightUrl: authPreflightUrls[0] || null,
|
|
215
|
+
authPreflightUrls,
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function normalizeConfigContract(config) {
|
|
220
|
+
const target = normalizeTargetBlock(config);
|
|
221
|
+
const scenarios = Array.isArray(config.scenarios)
|
|
222
|
+
? config.scenarios.map((scenario) => normalizeScenarioContract(scenario, target))
|
|
223
|
+
: [];
|
|
224
|
+
|
|
225
|
+
return {
|
|
226
|
+
...config,
|
|
227
|
+
target,
|
|
228
|
+
scenarios,
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function validateNormalizedConfig(config) {
|
|
233
|
+
const errors = [];
|
|
234
|
+
|
|
235
|
+
if (!config.target || typeof config.target !== "object") {
|
|
236
|
+
return {
|
|
237
|
+
valid: true,
|
|
238
|
+
errors,
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (!TARGET_TIERS.has(config.target.tier)) {
|
|
243
|
+
errors.push(`Invalid target tier "${config.target.tier}"`);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (!TARGET_AUTH_MODES.has(config.target.defaultAuthMode)) {
|
|
247
|
+
errors.push(`Invalid target.defaultAuthMode "${config.target.defaultAuthMode}"`);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
for (const scenario of config.scenarios || []) {
|
|
251
|
+
if (!SCENARIO_CAPTURE_CLASSES.has(scenario.captureClass)) {
|
|
252
|
+
errors.push(
|
|
253
|
+
`Scenario "${scenario.key}" has invalid captureClass "${scenario.captureClass}"`,
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
if (!PUBLISH_POLICIES.has(scenario.publishPolicy)) {
|
|
257
|
+
errors.push(
|
|
258
|
+
`Scenario "${scenario.key}" has invalid publishPolicy "${scenario.publishPolicy}"`,
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
return {
|
|
264
|
+
valid: errors.length === 0,
|
|
265
|
+
errors,
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function getCertifiedScenarioKeys(config, explicitScenarioKeys = null) {
|
|
270
|
+
if (Array.isArray(explicitScenarioKeys) && explicitScenarioKeys.length > 0) {
|
|
271
|
+
return explicitScenarioKeys;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const configured = normalizeStringArray(config?.target?.certificationScenarioKeys);
|
|
275
|
+
if (configured.length > 0) {
|
|
276
|
+
return configured;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
return (config?.scenarios || []).map((scenario) => scenario.key);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
module.exports = {
|
|
283
|
+
TARGET_TIERS,
|
|
284
|
+
TARGET_AUTH_MODES,
|
|
285
|
+
SCENARIO_CAPTURE_CLASSES,
|
|
286
|
+
PUBLISH_POLICIES,
|
|
287
|
+
normalizeConfigContract,
|
|
288
|
+
normalizeTargetBlock,
|
|
289
|
+
normalizeScenarioContract,
|
|
290
|
+
validateNormalizedConfig,
|
|
291
|
+
getCertifiedScenarioKeys,
|
|
292
|
+
};
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
const path = require("path");
|
|
2
|
+
const config = require("./config");
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Get the platform URL from settings, falling back to production.
|
|
6
|
+
* @param {Object} settings - CLI settings object
|
|
7
|
+
* @returns {string} Platform URL
|
|
8
|
+
*/
|
|
9
|
+
function getPlatformUrl(settings) {
|
|
10
|
+
if (settings?.platformUrl) {
|
|
11
|
+
return settings.platformUrl;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const envUrl = process.env.RESHOT_API_BASE_URL;
|
|
15
|
+
if (envUrl) {
|
|
16
|
+
return envUrl.replace(/\/api\/?$/, "");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return "https://reshot.dev";
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Handle API errors and detect if re-auth is needed.
|
|
24
|
+
* @param {Error} error - The error from API call
|
|
25
|
+
* @param {Object} res - Express response object
|
|
26
|
+
* @returns {Object|null} Response if error was handled, null otherwise
|
|
27
|
+
*/
|
|
28
|
+
function handleApiError(error, res) {
|
|
29
|
+
if (config.isAuthError(error)) {
|
|
30
|
+
const errorMsg =
|
|
31
|
+
error.response?.data?.error ||
|
|
32
|
+
error.message ||
|
|
33
|
+
"API key is invalid or expired";
|
|
34
|
+
return res.status(401).json(config.createAuthErrorResponse(errorMsg));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Generate all possible variant combinations from dimensions.
|
|
42
|
+
* @param {Object} dimensions - Variant dimensions config
|
|
43
|
+
* @param {string[]} dimensionKeys - Which dimensions to include
|
|
44
|
+
* @returns {Array<Object>} Array of variant objects
|
|
45
|
+
*/
|
|
46
|
+
function generateVariantCombinations(dimensions, dimensionKeys = []) {
|
|
47
|
+
if (!dimensions || dimensionKeys.length === 0) {
|
|
48
|
+
return [];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const dimensionOptions = dimensionKeys
|
|
52
|
+
.map((key) => {
|
|
53
|
+
const dim = dimensions[key];
|
|
54
|
+
if (!dim?.options) return [];
|
|
55
|
+
return Object.keys(dim.options).map((optKey) => ({
|
|
56
|
+
dimension: key,
|
|
57
|
+
option: optKey,
|
|
58
|
+
}));
|
|
59
|
+
})
|
|
60
|
+
.filter((opts) => opts.length > 0);
|
|
61
|
+
|
|
62
|
+
if (dimensionOptions.length === 0) {
|
|
63
|
+
return [];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const combinations = cartesian(...dimensionOptions);
|
|
67
|
+
|
|
68
|
+
return combinations.map((combo) => {
|
|
69
|
+
const variant = {};
|
|
70
|
+
for (const { dimension, option } of combo) {
|
|
71
|
+
variant[dimension] = option;
|
|
72
|
+
}
|
|
73
|
+
return variant;
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function cartesian(...arrays) {
|
|
78
|
+
return arrays.reduce(
|
|
79
|
+
(acc, arr) => acc.flatMap((combo) => arr.map((item) => [...combo, item])),
|
|
80
|
+
[[]],
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Validate a path segment to prevent directory traversal attacks.
|
|
86
|
+
* @param {string} segment - Path segment to validate
|
|
87
|
+
* @returns {boolean} True if safe, false if potentially malicious
|
|
88
|
+
*/
|
|
89
|
+
function isValidPathSegment(segment) {
|
|
90
|
+
if (!segment || typeof segment !== "string") return false;
|
|
91
|
+
if (segment === "." || segment === "..") return false;
|
|
92
|
+
if (segment.includes("/") || segment.includes("\\")) return false;
|
|
93
|
+
if (segment.includes("\0")) return false;
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Validate that a resolved path stays within the expected base directory.
|
|
99
|
+
* @param {string} resolvedPath - Fully resolved path
|
|
100
|
+
* @param {string} baseDir - Expected base directory
|
|
101
|
+
* @returns {boolean} True if path is within base, false otherwise
|
|
102
|
+
*/
|
|
103
|
+
function isPathWithinBase(resolvedPath, baseDir) {
|
|
104
|
+
const normalizedBase = path.resolve(baseDir);
|
|
105
|
+
const normalizedPath = path.resolve(resolvedPath);
|
|
106
|
+
return (
|
|
107
|
+
normalizedPath.startsWith(normalizedBase + path.sep) ||
|
|
108
|
+
normalizedPath === normalizedBase
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
module.exports = {
|
|
113
|
+
generateVariantCombinations,
|
|
114
|
+
getPlatformUrl,
|
|
115
|
+
handleApiError,
|
|
116
|
+
isPathWithinBase,
|
|
117
|
+
isValidPathSegment,
|
|
118
|
+
};
|