mirror of https://github.com/ghostfolio/ghostfolio
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
850 lines
29 KiB
850 lines
29 KiB
/*
|
|
Stencil Screenshot v4.38.0 | MIT Licensed | https://stenciljs.com
|
|
*/
|
|
"use strict";
|
|
var __create = Object.create;
|
|
var __defProp = Object.defineProperty;
|
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
var __getProtoOf = Object.getPrototypeOf;
|
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
var __export = (target, all) => {
|
|
for (var name in all)
|
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
};
|
|
var __copyProps = (to, from, except, desc) => {
|
|
if (from && typeof from === "object" || typeof from === "function") {
|
|
for (let key of __getOwnPropNames(from))
|
|
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
}
|
|
return to;
|
|
};
|
|
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
// If the importer is in node compatibility mode or this is not an ESM
|
|
// file that has been converted to a CommonJS file using a Babel-
|
|
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
mod
|
|
));
|
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
|
|
// src/screenshot/index.ts
|
|
var index_exports = {};
|
|
__export(index_exports, {
|
|
ScreenshotConnector: () => ScreenshotConnector,
|
|
ScreenshotLocalConnector: () => ScreenshotLocalConnector
|
|
});
|
|
module.exports = __toCommonJS(index_exports);
|
|
|
|
// src/screenshot/connector-base.ts
|
|
var import_os = require("os");
|
|
var import_path2 = require("path");
|
|
|
|
// src/screenshot/screenshot-fs.ts
|
|
var import_fs = __toESM(require("fs"));
|
|
var import_path = __toESM(require("path"));
|
|
function fileExists(filePath) {
|
|
return new Promise((resolve) => {
|
|
import_fs.default.access(filePath, (err2) => resolve(!err2));
|
|
});
|
|
}
|
|
function readFile(filePath) {
|
|
return new Promise((resolve, reject) => {
|
|
import_fs.default.readFile(filePath, "utf-8", (err2, data) => {
|
|
if (err2) {
|
|
reject(err2);
|
|
} else {
|
|
resolve(data);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
function readFileBuffer(filePath) {
|
|
return new Promise((resolve, reject) => {
|
|
import_fs.default.readFile(filePath, (err2, data) => {
|
|
if (err2) {
|
|
reject(err2);
|
|
} else {
|
|
resolve(data);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
function writeFile(filePath, data) {
|
|
return new Promise((resolve, reject) => {
|
|
import_fs.default.writeFile(filePath, data, (err2) => {
|
|
if (err2) {
|
|
reject(err2);
|
|
} else {
|
|
resolve();
|
|
}
|
|
});
|
|
});
|
|
}
|
|
function mkDir(filePath) {
|
|
return new Promise((resolve) => {
|
|
import_fs.default.mkdir(filePath, () => {
|
|
resolve();
|
|
});
|
|
});
|
|
}
|
|
function rmDir(filePath) {
|
|
return new Promise((resolve) => {
|
|
import_fs.default.rmdir(filePath, () => {
|
|
resolve();
|
|
});
|
|
});
|
|
}
|
|
async function emptyDir(dir) {
|
|
const files = await readDir(dir);
|
|
const promises = files.map(async (fileName) => {
|
|
const filePath = import_path.default.join(dir, fileName);
|
|
const isDirFile = await isFile(filePath);
|
|
if (isDirFile) {
|
|
await unlink(filePath);
|
|
}
|
|
});
|
|
await Promise.all(promises);
|
|
}
|
|
async function readDir(dir) {
|
|
return new Promise((resolve) => {
|
|
import_fs.default.readdir(dir, (err2, files) => {
|
|
if (err2) {
|
|
resolve([]);
|
|
} else {
|
|
resolve(files);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
async function isFile(itemPath) {
|
|
return new Promise((resolve) => {
|
|
import_fs.default.stat(itemPath, (err2, stat) => {
|
|
if (err2) {
|
|
resolve(false);
|
|
} else {
|
|
resolve(stat.isFile());
|
|
}
|
|
});
|
|
});
|
|
}
|
|
async function unlink(filePath) {
|
|
return new Promise((resolve) => {
|
|
import_fs.default.unlink(filePath, () => {
|
|
resolve();
|
|
});
|
|
});
|
|
}
|
|
|
|
// src/screenshot/connector-base.ts
|
|
var ScreenshotConnector = class {
|
|
constructor() {
|
|
this.screenshotDirName = "screenshot";
|
|
this.imagesDirName = "images";
|
|
this.buildsDirName = "builds";
|
|
this.masterBuildFileName = "master.json";
|
|
this.screenshotCacheFileName = "screenshot-cache.json";
|
|
}
|
|
async initBuild(opts) {
|
|
this.logger = opts.logger;
|
|
this.buildId = opts.buildId;
|
|
this.buildMessage = opts.buildMessage || "";
|
|
this.buildAuthor = opts.buildAuthor;
|
|
this.buildUrl = opts.buildUrl;
|
|
this.previewUrl = opts.previewUrl;
|
|
this.buildTimestamp = typeof opts.buildTimestamp === "number" ? opts.buildTimestamp : Date.now();
|
|
this.cacheDir = opts.cacheDir;
|
|
this.packageDir = opts.packageDir;
|
|
this.rootDir = opts.rootDir;
|
|
this.appNamespace = opts.appNamespace;
|
|
this.waitBeforeScreenshot = opts.waitBeforeScreenshot;
|
|
this.pixelmatchModulePath = opts.pixelmatchModulePath;
|
|
if (!opts.logger) {
|
|
throw new Error(`logger option required`);
|
|
}
|
|
if (typeof opts.buildId !== "string") {
|
|
throw new Error(`buildId option required`);
|
|
}
|
|
if (typeof opts.cacheDir !== "string") {
|
|
throw new Error(`cacheDir option required`);
|
|
}
|
|
if (typeof opts.packageDir !== "string") {
|
|
throw new Error(`packageDir option required`);
|
|
}
|
|
if (typeof opts.rootDir !== "string") {
|
|
throw new Error(`rootDir option required`);
|
|
}
|
|
this.updateMaster = !!opts.updateMaster;
|
|
this.allowableMismatchedPixels = opts.allowableMismatchedPixels;
|
|
this.allowableMismatchedRatio = opts.allowableMismatchedRatio;
|
|
this.pixelmatchThreshold = opts.pixelmatchThreshold;
|
|
this.logger.debug(`screenshot build: ${this.buildId}, ${this.buildMessage}, updateMaster: ${this.updateMaster}`);
|
|
this.logger.debug(
|
|
`screenshot, allowableMismatchedPixels: ${this.allowableMismatchedPixels}, allowableMismatchedRatio: ${this.allowableMismatchedRatio}, pixelmatchThreshold: ${this.pixelmatchThreshold}`
|
|
);
|
|
if (typeof opts.screenshotDirName === "string") {
|
|
this.screenshotDirName = opts.screenshotDirName;
|
|
}
|
|
if (typeof opts.imagesDirName === "string") {
|
|
this.imagesDirName = opts.imagesDirName;
|
|
}
|
|
if (typeof opts.buildsDirName === "string") {
|
|
this.buildsDirName = opts.buildsDirName;
|
|
}
|
|
this.screenshotDir = (0, import_path2.join)(this.rootDir, this.screenshotDirName);
|
|
this.imagesDir = (0, import_path2.join)(this.screenshotDir, this.imagesDirName);
|
|
this.buildsDir = (0, import_path2.join)(this.screenshotDir, this.buildsDirName);
|
|
this.masterBuildFilePath = (0, import_path2.join)(this.buildsDir, this.masterBuildFileName);
|
|
this.screenshotCacheFilePath = (0, import_path2.join)(this.cacheDir, this.screenshotCacheFileName);
|
|
this.currentBuildDir = (0, import_path2.join)((0, import_os.tmpdir)(), "screenshot-build-" + this.buildId);
|
|
this.logger.debug(`screenshotDirPath: ${this.screenshotDir}`);
|
|
this.logger.debug(`imagesDirPath: ${this.imagesDir}`);
|
|
this.logger.debug(`buildsDirPath: ${this.buildsDir}`);
|
|
this.logger.debug(`currentBuildDir: ${this.currentBuildDir}`);
|
|
this.logger.debug(`cacheDir: ${this.cacheDir}`);
|
|
await mkDir(this.screenshotDir);
|
|
await Promise.all([
|
|
mkDir(this.imagesDir),
|
|
mkDir(this.buildsDir),
|
|
mkDir(this.currentBuildDir),
|
|
mkDir(this.cacheDir)
|
|
]);
|
|
}
|
|
async pullMasterBuild() {
|
|
}
|
|
async getMasterBuild() {
|
|
try {
|
|
const masterBuild = JSON.parse(await readFile(this.masterBuildFilePath));
|
|
return masterBuild;
|
|
} catch (e) {
|
|
}
|
|
return null;
|
|
}
|
|
async completeBuild(masterBuild) {
|
|
const filePaths = (await readDir(this.currentBuildDir)).map((f) => (0, import_path2.join)(this.currentBuildDir, f)).filter((f) => f.endsWith(".json"));
|
|
const screenshots = await Promise.all(filePaths.map(async (f) => JSON.parse(await readFile(f))));
|
|
this.sortScreenshots(screenshots);
|
|
if (!masterBuild) {
|
|
masterBuild = {
|
|
id: this.buildId,
|
|
message: this.buildMessage,
|
|
author: this.buildAuthor,
|
|
url: this.buildUrl,
|
|
previewUrl: this.previewUrl,
|
|
appNamespace: this.appNamespace,
|
|
timestamp: this.buildTimestamp,
|
|
screenshots
|
|
};
|
|
}
|
|
const results = {
|
|
appNamespace: this.appNamespace,
|
|
masterBuild,
|
|
currentBuild: {
|
|
id: this.buildId,
|
|
message: this.buildMessage,
|
|
author: this.buildAuthor,
|
|
url: this.buildUrl,
|
|
previewUrl: this.previewUrl,
|
|
appNamespace: this.appNamespace,
|
|
timestamp: this.buildTimestamp,
|
|
screenshots
|
|
},
|
|
compare: {
|
|
id: `${masterBuild.id}-${this.buildId}`,
|
|
a: {
|
|
id: masterBuild.id,
|
|
message: masterBuild.message,
|
|
author: masterBuild.author,
|
|
url: masterBuild.url,
|
|
previewUrl: masterBuild.previewUrl
|
|
},
|
|
b: {
|
|
id: this.buildId,
|
|
message: this.buildMessage,
|
|
author: this.buildAuthor,
|
|
url: this.buildUrl,
|
|
previewUrl: this.previewUrl
|
|
},
|
|
url: null,
|
|
appNamespace: this.appNamespace,
|
|
timestamp: this.buildTimestamp,
|
|
diffs: []
|
|
}
|
|
};
|
|
results.currentBuild.screenshots.forEach((screenshot) => {
|
|
screenshot.diff.device = screenshot.diff.device || screenshot.diff.userAgent;
|
|
results.compare.diffs.push(screenshot.diff);
|
|
delete screenshot.diff;
|
|
});
|
|
this.sortCompares(results.compare.diffs);
|
|
await emptyDir(this.currentBuildDir);
|
|
await rmDir(this.currentBuildDir);
|
|
return results;
|
|
}
|
|
async publishBuild(results) {
|
|
return results;
|
|
}
|
|
async generateJsonpDataUris(build) {
|
|
if (build && Array.isArray(build.screenshots)) {
|
|
for (let i = 0; i < build.screenshots.length; i++) {
|
|
const screenshot = build.screenshots[i];
|
|
const jsonpFileName = `screenshot_${screenshot.image}.js`;
|
|
const jsonFilePath = (0, import_path2.join)(this.cacheDir, jsonpFileName);
|
|
const jsonpExists = await fileExists(jsonFilePath);
|
|
if (!jsonpExists) {
|
|
const imageFilePath = (0, import_path2.join)(this.imagesDir, screenshot.image);
|
|
const imageBuf = await readFileBuffer(imageFilePath);
|
|
const jsonpContent = `loadScreenshot("${screenshot.image}","data:image/png;base64,${imageBuf.toString(
|
|
"base64"
|
|
)}");`;
|
|
await writeFile(jsonFilePath, jsonpContent);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
async getScreenshotCache() {
|
|
return null;
|
|
}
|
|
async updateScreenshotCache(screenshotCache, buildResults) {
|
|
screenshotCache = screenshotCache || {};
|
|
screenshotCache.timestamp = this.buildTimestamp;
|
|
screenshotCache.lastBuildId = this.buildId;
|
|
screenshotCache.size = 0;
|
|
screenshotCache.items = screenshotCache.items || [];
|
|
if (buildResults && buildResults.compare && Array.isArray(buildResults.compare.diffs)) {
|
|
buildResults.compare.diffs.forEach((diff) => {
|
|
if (typeof diff.cacheKey !== "string") {
|
|
return;
|
|
}
|
|
if (diff.imageA === diff.imageB) {
|
|
return;
|
|
}
|
|
const existingItem = screenshotCache.items.find((i) => i.key === diff.cacheKey);
|
|
if (existingItem) {
|
|
existingItem.ts = this.buildTimestamp;
|
|
} else {
|
|
screenshotCache.items.push({
|
|
key: diff.cacheKey,
|
|
ts: this.buildTimestamp,
|
|
mp: diff.mismatchedPixels
|
|
});
|
|
}
|
|
});
|
|
}
|
|
screenshotCache.items.sort((a, b) => {
|
|
if (a.ts > b.ts) return -1;
|
|
if (a.ts < b.ts) return 1;
|
|
if (a.mp > b.mp) return -1;
|
|
if (a.mp < b.mp) return 1;
|
|
return 0;
|
|
});
|
|
screenshotCache.items = screenshotCache.items.slice(0, 1e3);
|
|
screenshotCache.size = screenshotCache.items.length;
|
|
return screenshotCache;
|
|
}
|
|
toJson(masterBuild, screenshotCache) {
|
|
const masterScreenshots = {};
|
|
if (masterBuild && Array.isArray(masterBuild.screenshots)) {
|
|
masterBuild.screenshots.forEach((masterScreenshot) => {
|
|
masterScreenshots[masterScreenshot.id] = masterScreenshot.image;
|
|
});
|
|
}
|
|
const mismatchCache = {};
|
|
if (screenshotCache && Array.isArray(screenshotCache.items)) {
|
|
screenshotCache.items.forEach((cacheItem) => {
|
|
mismatchCache[cacheItem.key] = cacheItem.mp;
|
|
});
|
|
}
|
|
const screenshotBuild = {
|
|
buildId: this.buildId,
|
|
rootDir: this.rootDir,
|
|
screenshotDir: this.screenshotDir,
|
|
imagesDir: this.imagesDir,
|
|
buildsDir: this.buildsDir,
|
|
masterScreenshots,
|
|
cache: mismatchCache,
|
|
currentBuildDir: this.currentBuildDir,
|
|
updateMaster: this.updateMaster,
|
|
allowableMismatchedPixels: this.allowableMismatchedPixels,
|
|
allowableMismatchedRatio: this.allowableMismatchedRatio,
|
|
pixelmatchThreshold: this.pixelmatchThreshold,
|
|
timeoutBeforeScreenshot: this.waitBeforeScreenshot,
|
|
pixelmatchModulePath: this.pixelmatchModulePath
|
|
};
|
|
return JSON.stringify(screenshotBuild);
|
|
}
|
|
sortScreenshots(screenshots) {
|
|
return screenshots.sort((a, b) => {
|
|
if (a.desc && b.desc) {
|
|
if (a.desc.toLowerCase() < b.desc.toLowerCase()) return -1;
|
|
if (a.desc.toLowerCase() > b.desc.toLowerCase()) return 1;
|
|
}
|
|
if (a.device && b.device) {
|
|
if (a.device.toLowerCase() < b.device.toLowerCase()) return -1;
|
|
if (a.device.toLowerCase() > b.device.toLowerCase()) return 1;
|
|
}
|
|
if (a.userAgent && b.userAgent) {
|
|
if (a.userAgent.toLowerCase() < b.userAgent.toLowerCase()) return -1;
|
|
if (a.userAgent.toLowerCase() > b.userAgent.toLowerCase()) return 1;
|
|
}
|
|
if (a.width < b.width) return -1;
|
|
if (a.width > b.width) return 1;
|
|
if (a.height < b.height) return -1;
|
|
if (a.height > b.height) return 1;
|
|
if (a.id < b.id) return -1;
|
|
if (a.id > b.id) return 1;
|
|
return 0;
|
|
});
|
|
}
|
|
sortCompares(compares) {
|
|
return compares.sort((a, b) => {
|
|
if (a.allowableMismatchedPixels > b.allowableMismatchedPixels) return -1;
|
|
if (a.allowableMismatchedPixels < b.allowableMismatchedPixels) return 1;
|
|
if (a.allowableMismatchedRatio > b.allowableMismatchedRatio) return -1;
|
|
if (a.allowableMismatchedRatio < b.allowableMismatchedRatio) return 1;
|
|
if (a.desc && b.desc) {
|
|
if (a.desc.toLowerCase() < b.desc.toLowerCase()) return -1;
|
|
if (a.desc.toLowerCase() > b.desc.toLowerCase()) return 1;
|
|
}
|
|
if (a.device && b.device) {
|
|
if (a.device.toLowerCase() < b.device.toLowerCase()) return -1;
|
|
if (a.device.toLowerCase() > b.device.toLowerCase()) return 1;
|
|
}
|
|
if (a.userAgent && b.userAgent) {
|
|
if (a.userAgent.toLowerCase() < b.userAgent.toLowerCase()) return -1;
|
|
if (a.userAgent.toLowerCase() > b.userAgent.toLowerCase()) return 1;
|
|
}
|
|
if (a.width < b.width) return -1;
|
|
if (a.width > b.width) return 1;
|
|
if (a.height < b.height) return -1;
|
|
if (a.height > b.height) return 1;
|
|
if (a.id < b.id) return -1;
|
|
if (a.id > b.id) return 1;
|
|
return 0;
|
|
});
|
|
}
|
|
};
|
|
|
|
// src/utils/path.ts
|
|
var normalizePath = (path2, relativize = true) => {
|
|
if (typeof path2 !== "string") {
|
|
throw new Error(`invalid path to normalize`);
|
|
}
|
|
path2 = normalizeSlashes(path2.trim());
|
|
const components = pathComponents(path2, getRootLength(path2));
|
|
const reducedComponents = reducePathComponents(components);
|
|
const rootPart = reducedComponents[0];
|
|
const secondPart = reducedComponents[1];
|
|
const normalized = rootPart + reducedComponents.slice(1).join("/");
|
|
if (normalized === "") {
|
|
return ".";
|
|
}
|
|
if (rootPart === "" && secondPart && path2.includes("/") && !secondPart.startsWith(".") && !secondPart.startsWith("@") && relativize) {
|
|
return "./" + normalized;
|
|
}
|
|
return normalized;
|
|
};
|
|
var normalizeSlashes = (path2) => path2.replace(backslashRegExp, "/");
|
|
var altDirectorySeparator = "\\";
|
|
var urlSchemeSeparator = "://";
|
|
var backslashRegExp = /\\/g;
|
|
var reducePathComponents = (components) => {
|
|
if (!Array.isArray(components) || components.length === 0) {
|
|
return [];
|
|
}
|
|
const reduced = [components[0]];
|
|
for (let i = 1; i < components.length; i++) {
|
|
const component = components[i];
|
|
if (!component) continue;
|
|
if (component === ".") continue;
|
|
if (component === "..") {
|
|
if (reduced.length > 1) {
|
|
if (reduced[reduced.length - 1] !== "..") {
|
|
reduced.pop();
|
|
continue;
|
|
}
|
|
} else if (reduced[0]) continue;
|
|
}
|
|
reduced.push(component);
|
|
}
|
|
return reduced;
|
|
};
|
|
var getRootLength = (path2) => {
|
|
const rootLength = getEncodedRootLength(path2);
|
|
return rootLength < 0 ? ~rootLength : rootLength;
|
|
};
|
|
var getEncodedRootLength = (path2) => {
|
|
if (!path2) return 0;
|
|
const ch0 = path2.charCodeAt(0);
|
|
if (ch0 === 47 /* slash */ || ch0 === 92 /* backslash */) {
|
|
if (path2.charCodeAt(1) !== ch0) return 1;
|
|
const p1 = path2.indexOf(ch0 === 47 /* slash */ ? "/" : altDirectorySeparator, 2);
|
|
if (p1 < 0) return path2.length;
|
|
return p1 + 1;
|
|
}
|
|
if (isVolumeCharacter(ch0) && path2.charCodeAt(1) === 58 /* colon */) {
|
|
const ch2 = path2.charCodeAt(2);
|
|
if (ch2 === 47 /* slash */ || ch2 === 92 /* backslash */) return 3;
|
|
if (path2.length === 2) return 2;
|
|
}
|
|
const schemeEnd = path2.indexOf(urlSchemeSeparator);
|
|
if (schemeEnd !== -1) {
|
|
const authorityStart = schemeEnd + urlSchemeSeparator.length;
|
|
const authorityEnd = path2.indexOf("/", authorityStart);
|
|
if (authorityEnd !== -1) {
|
|
const scheme = path2.slice(0, schemeEnd);
|
|
const authority = path2.slice(authorityStart, authorityEnd);
|
|
if (scheme === "file" && (authority === "" || authority === "localhost") && isVolumeCharacter(path2.charCodeAt(authorityEnd + 1))) {
|
|
const volumeSeparatorEnd = getFileUrlVolumeSeparatorEnd(path2, authorityEnd + 2);
|
|
if (volumeSeparatorEnd !== -1) {
|
|
if (path2.charCodeAt(volumeSeparatorEnd) === 47 /* slash */) {
|
|
return ~(volumeSeparatorEnd + 1);
|
|
}
|
|
if (volumeSeparatorEnd === path2.length) {
|
|
return ~volumeSeparatorEnd;
|
|
}
|
|
}
|
|
}
|
|
return ~(authorityEnd + 1);
|
|
}
|
|
return ~path2.length;
|
|
}
|
|
return 0;
|
|
};
|
|
var isVolumeCharacter = (charCode) => charCode >= 97 /* a */ && charCode <= 122 /* z */ || charCode >= 65 /* A */ && charCode <= 90 /* Z */;
|
|
var getFileUrlVolumeSeparatorEnd = (url, start) => {
|
|
const ch0 = url.charCodeAt(start);
|
|
if (ch0 === 58 /* colon */) return start + 1;
|
|
if (ch0 === 37 /* percent */ && url.charCodeAt(start + 1) === 51 /* _3 */) {
|
|
const ch2 = url.charCodeAt(start + 2);
|
|
if (ch2 === 97 /* a */ || ch2 === 65 /* A */) return start + 3;
|
|
}
|
|
return -1;
|
|
};
|
|
var pathComponents = (path2, rootLength) => {
|
|
const root = path2.substring(0, rootLength);
|
|
const rest = path2.substring(rootLength).split("/");
|
|
const restLen = rest.length;
|
|
if (restLen > 0 && !rest[restLen - 1]) {
|
|
rest.pop();
|
|
}
|
|
return [root, ...rest];
|
|
};
|
|
|
|
// src/utils/regular-expression.ts
|
|
var escapeRegExpSpecialCharacters = (text) => {
|
|
return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
};
|
|
|
|
// src/utils/result.ts
|
|
var result_exports = {};
|
|
__export(result_exports, {
|
|
err: () => err,
|
|
map: () => map,
|
|
ok: () => ok,
|
|
unwrap: () => unwrap,
|
|
unwrapErr: () => unwrapErr
|
|
});
|
|
var ok = (value) => ({
|
|
isOk: true,
|
|
isErr: false,
|
|
value
|
|
});
|
|
var err = (value) => ({
|
|
isOk: false,
|
|
isErr: true,
|
|
value
|
|
});
|
|
function map(result, fn) {
|
|
if (result.isOk) {
|
|
const val = fn(result.value);
|
|
if (val instanceof Promise) {
|
|
return val.then((newVal) => ok(newVal));
|
|
} else {
|
|
return ok(val);
|
|
}
|
|
}
|
|
if (result.isErr) {
|
|
const value = result.value;
|
|
return err(value);
|
|
}
|
|
throw "should never get here";
|
|
}
|
|
var unwrap = (result) => {
|
|
if (result.isOk) {
|
|
return result.value;
|
|
} else {
|
|
throw result.value;
|
|
}
|
|
};
|
|
var unwrapErr = (result) => {
|
|
if (result.isErr) {
|
|
return result.value;
|
|
} else {
|
|
throw result.value;
|
|
}
|
|
};
|
|
|
|
// src/app-data/index.ts
|
|
var BUILD = {
|
|
allRenderFn: false,
|
|
element: true,
|
|
event: true,
|
|
hasRenderFn: true,
|
|
hostListener: true,
|
|
hostListenerTargetWindow: true,
|
|
hostListenerTargetDocument: true,
|
|
hostListenerTargetBody: true,
|
|
hostListenerTargetParent: false,
|
|
hostListenerTarget: true,
|
|
member: true,
|
|
method: true,
|
|
mode: true,
|
|
observeAttribute: true,
|
|
prop: true,
|
|
propMutable: true,
|
|
reflect: true,
|
|
scoped: true,
|
|
shadowDom: true,
|
|
slot: true,
|
|
cssAnnotations: true,
|
|
state: true,
|
|
style: true,
|
|
formAssociated: false,
|
|
svg: true,
|
|
updatable: true,
|
|
vdomAttribute: true,
|
|
vdomXlink: true,
|
|
vdomClass: true,
|
|
vdomFunctional: true,
|
|
vdomKey: true,
|
|
vdomListener: true,
|
|
vdomRef: true,
|
|
vdomPropOrAttr: true,
|
|
vdomRender: true,
|
|
vdomStyle: true,
|
|
vdomText: true,
|
|
propChangeCallback: true,
|
|
taskQueue: true,
|
|
hotModuleReplacement: false,
|
|
isDebug: false,
|
|
isDev: false,
|
|
isTesting: false,
|
|
hydrateServerSide: false,
|
|
hydrateClientSide: false,
|
|
lifecycleDOMEvents: false,
|
|
lazyLoad: false,
|
|
profile: false,
|
|
slotRelocation: true,
|
|
// TODO(STENCIL-914): remove this option when `experimentalSlotFixes` is the default behavior
|
|
appendChildSlotFix: false,
|
|
// TODO(STENCIL-914): remove this option when `experimentalSlotFixes` is the default behavior
|
|
cloneNodeFix: false,
|
|
hydratedAttribute: false,
|
|
hydratedClass: true,
|
|
// TODO(STENCIL-1305): remove this option
|
|
scriptDataOpts: false,
|
|
// TODO(STENCIL-914): remove this option when `experimentalSlotFixes` is the default behavior
|
|
scopedSlotTextContentFix: false,
|
|
// TODO(STENCIL-854): Remove code related to legacy shadowDomShim field
|
|
shadowDomShim: false,
|
|
// TODO(STENCIL-914): remove this option when `experimentalSlotFixes` is the default behavior
|
|
slotChildNodesFix: false,
|
|
invisiblePrehydration: true,
|
|
propBoolean: true,
|
|
propNumber: true,
|
|
propString: true,
|
|
constructableCSS: true,
|
|
devTools: false,
|
|
shadowDelegatesFocus: true,
|
|
initializeNextTick: false,
|
|
asyncLoading: true,
|
|
asyncQueue: false,
|
|
transformTagName: false,
|
|
attachStyles: true,
|
|
// TODO(STENCIL-914): remove this option when `experimentalSlotFixes` is the default behavior
|
|
experimentalSlotFixes: false
|
|
};
|
|
|
|
// src/client/client-build.ts
|
|
var Build = {
|
|
isDev: BUILD.isDev ? true : false,
|
|
isBrowser: true,
|
|
isServer: false,
|
|
isTesting: BUILD.isTesting ? true : false
|
|
};
|
|
|
|
// src/client/client-log.ts
|
|
var STENCIL_DEV_MODE = BUILD.isTesting ? ["STENCIL:"] : [
|
|
"%cstencil",
|
|
"color: white;background:#4c47ff;font-weight: bold; font-size:10px; padding:2px 6px; border-radius: 5px"
|
|
];
|
|
|
|
// src/client/client-window.ts
|
|
var win = typeof window !== "undefined" ? window : {};
|
|
var H = win.HTMLElement || class {
|
|
};
|
|
var supportsShadow = BUILD.shadowDom;
|
|
var supportsConstructableStylesheets = BUILD.constructableCSS ? /* @__PURE__ */ (() => {
|
|
try {
|
|
new CSSStyleSheet();
|
|
return typeof new CSSStyleSheet().replaceSync === "function";
|
|
} catch (e) {
|
|
}
|
|
return false;
|
|
})() : false;
|
|
|
|
// src/utils/shadow-css.ts
|
|
/**
|
|
* @license
|
|
* Copyright Google Inc. All Rights Reserved.
|
|
*
|
|
* Use of this source code is governed by an MIT-style license that can be
|
|
* found in the LICENSE file at https://angular.io/license
|
|
*
|
|
* This file is a port of shadowCSS from `webcomponents.js` to TypeScript.
|
|
* https://github.com/webcomponents/webcomponentsjs/blob/4efecd7e0e/src/ShadowCSS/ShadowCSS.js
|
|
* https://github.com/angular/angular/blob/master/packages/compiler/src/shadow_css.ts
|
|
*/
|
|
var _polyfillHost = "-shadowcsshost";
|
|
var _polyfillSlotted = "-shadowcssslotted";
|
|
var _polyfillHostContext = "-shadowcsscontext";
|
|
var _parenSuffix = ")(?:\\(((?:\\([^)(]*\\)|[^)(]*)+?)\\))?([^,{]*)";
|
|
var _cssColonHostRe = new RegExp("(" + _polyfillHost + _parenSuffix, "gim");
|
|
var _cssColonHostContextRe = new RegExp("(" + _polyfillHostContext + _parenSuffix, "gim");
|
|
var _cssColonSlottedRe = new RegExp("(" + _polyfillSlotted + _parenSuffix, "gim");
|
|
var _polyfillHostNoCombinator = _polyfillHost + "-no-combinator";
|
|
var createSupportsRuleRe = (selector) => {
|
|
const safeSelector = escapeRegExpSpecialCharacters(selector);
|
|
return new RegExp(
|
|
// First capture group: match any context before the selector that's not inside @supports selector()
|
|
// Using negative lookahead to avoid matching inside @supports selector(...) condition
|
|
`(^|[^@]|@(?!supports\\s+selector\\s*\\([^{]*?${safeSelector}))(${safeSelector}\\b)`,
|
|
"g"
|
|
);
|
|
};
|
|
var _colonSlottedRe = createSupportsRuleRe("::slotted");
|
|
var _colonHostRe = createSupportsRuleRe(":host");
|
|
var _colonHostContextRe = createSupportsRuleRe(":host-context");
|
|
|
|
// src/runtime/vdom/set-accessor.ts
|
|
var CAPTURE_EVENT_SUFFIX = "Capture";
|
|
var CAPTURE_EVENT_REGEX = new RegExp(CAPTURE_EVENT_SUFFIX + "$");
|
|
|
|
// src/runtime/mixin.ts
|
|
var baseClass = BUILD.lazyLoad ? class {
|
|
} : globalThis.HTMLElement || class {
|
|
};
|
|
|
|
// src/utils/util.ts
|
|
var lowerPathParam = (fn) => (p) => fn(p.toLowerCase());
|
|
var isDtsFile = lowerPathParam((p) => p.endsWith(".d.ts") || p.endsWith(".d.mts") || p.endsWith(".d.cts"));
|
|
var isTsFile = lowerPathParam(
|
|
(p) => !isDtsFile(p) && (p.endsWith(".ts") || p.endsWith(".mts") || p.endsWith(".cts"))
|
|
);
|
|
var isTsxFile = lowerPathParam(
|
|
(p) => p.endsWith(".tsx") || p.endsWith(".mtsx") || p.endsWith(".ctsx")
|
|
);
|
|
var isJsxFile = lowerPathParam(
|
|
(p) => p.endsWith(".jsx") || p.endsWith(".mjsx") || p.endsWith(".cjsx")
|
|
);
|
|
var isJsFile = lowerPathParam((p) => p.endsWith(".js") || p.endsWith(".mjs") || p.endsWith(".cjs"));
|
|
|
|
// src/screenshot/connector-local.ts
|
|
var import_path4 = require("path");
|
|
var ScreenshotLocalConnector = class extends ScreenshotConnector {
|
|
async publishBuild(results) {
|
|
if (this.updateMaster || !results.masterBuild) {
|
|
results.masterBuild = {
|
|
id: "master",
|
|
message: "Master",
|
|
appNamespace: this.appNamespace,
|
|
timestamp: Date.now(),
|
|
screenshots: []
|
|
};
|
|
}
|
|
results.currentBuild.screenshots.forEach((currentScreenshot) => {
|
|
const masterHasScreenshot = results.masterBuild.screenshots.some((masterScreenshot) => {
|
|
return currentScreenshot.id === masterScreenshot.id;
|
|
});
|
|
if (!masterHasScreenshot) {
|
|
results.masterBuild.screenshots.push(Object.assign({}, currentScreenshot));
|
|
}
|
|
});
|
|
this.sortScreenshots(results.masterBuild.screenshots);
|
|
await writeFile(this.masterBuildFilePath, JSON.stringify(results.masterBuild, null, 2));
|
|
await this.generateJsonpDataUris(results.currentBuild);
|
|
const compareAppSourceDir = (0, import_path4.join)(this.packageDir, "screenshot", "compare");
|
|
const appSrcUrl = normalizePath((0, import_path4.relative)(this.screenshotDir, compareAppSourceDir));
|
|
const imagesUrl = normalizePath((0, import_path4.relative)(this.screenshotDir, this.imagesDir));
|
|
const jsonpUrl = normalizePath((0, import_path4.relative)(this.screenshotDir, this.cacheDir));
|
|
const compareAppHtml = createLocalCompareApp(
|
|
this.appNamespace,
|
|
appSrcUrl,
|
|
imagesUrl,
|
|
jsonpUrl,
|
|
results.masterBuild,
|
|
results.currentBuild
|
|
);
|
|
const compareAppFileName = "compare.html";
|
|
const compareAppFilePath = (0, import_path4.join)(this.screenshotDir, compareAppFileName);
|
|
await writeFile(compareAppFilePath, compareAppHtml);
|
|
const gitIgnorePath = (0, import_path4.join)(this.screenshotDir, ".gitignore");
|
|
const gitIgnoreExists = await fileExists(gitIgnorePath);
|
|
if (!gitIgnoreExists) {
|
|
const content = [this.imagesDirName, this.buildsDirName, compareAppFileName];
|
|
await writeFile(gitIgnorePath, content.join("\n"));
|
|
}
|
|
const url = new URL(`file://${compareAppFilePath}`);
|
|
results.compare.url = url.href;
|
|
return results;
|
|
}
|
|
async getScreenshotCache() {
|
|
let screenshotCache = null;
|
|
try {
|
|
screenshotCache = JSON.parse(await readFile(this.screenshotCacheFilePath));
|
|
} catch (e) {
|
|
}
|
|
return screenshotCache;
|
|
}
|
|
async updateScreenshotCache(cache, buildResults) {
|
|
cache = await super.updateScreenshotCache(cache, buildResults);
|
|
await writeFile(this.screenshotCacheFilePath, JSON.stringify(cache, null, 2));
|
|
return cache;
|
|
}
|
|
};
|
|
function createLocalCompareApp(namespace, appSrcUrl, imagesUrl, jsonpUrl, a, b) {
|
|
return `<!doctype html>
|
|
<html dir="ltr" lang="en">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<title>Local ${namespace || ""} - Stencil Screenshot Visual Diff</title>
|
|
<meta name="viewport" content="viewport-fit=cover, width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
|
<meta http-equiv="x-ua-compatible" content="IE=Edge">
|
|
<link href="${appSrcUrl}/build/app.css" rel="stylesheet">
|
|
<script type="module" src="${appSrcUrl}/build/app.esm.js"></script>
|
|
<script nomodule src="${appSrcUrl}/build/app.js"></script>
|
|
<link rel="icon" type="image/x-icon" href="${appSrcUrl}/assets/favicon.ico">
|
|
</head>
|
|
<body>
|
|
<script>
|
|
(function() {
|
|
var app = document.createElement('screenshot-compare');
|
|
app.appSrcUrl = '${appSrcUrl}';
|
|
app.imagesUrl = '${imagesUrl}/';
|
|
app.jsonpUrl = '${jsonpUrl}/';
|
|
app.a = ${JSON.stringify(a)};
|
|
app.b = ${JSON.stringify(b)};
|
|
document.body.appendChild(app);
|
|
})();
|
|
</script>
|
|
</body>
|
|
</html>`;
|
|
}
|
|
// Annotate the CommonJS export names for ESM import in node:
|
|
0 && (module.exports = {
|
|
ScreenshotConnector,
|
|
ScreenshotLocalConnector
|
|
});
|
|
|