storybook
Version:
Storybook: Develop, document, and test UI components in isolation
305 lines (299 loc) • 11.2 kB
JavaScript
import CJS_COMPAT_NODE_URL_q99y7iqlbzn from 'node:url';
import CJS_COMPAT_NODE_PATH_q99y7iqlbzn from 'node:path';
import CJS_COMPAT_NODE_MODULE_q99y7iqlbzn from "node:module";
var __filename = CJS_COMPAT_NODE_URL_q99y7iqlbzn.fileURLToPath(import.meta.url);
var __dirname = CJS_COMPAT_NODE_PATH_q99y7iqlbzn.dirname(__filename);
var require = CJS_COMPAT_NODE_MODULE_q99y7iqlbzn.createRequire(import.meta.url);
// ------------------------------------------------------------
// end of CJS compatibility banner, injected by Storybook's esbuild configuration
// ------------------------------------------------------------
import {
require_build
} from "./chunk-UXQJJRYH.js";
import {
resolvePackageDir
} from "./chunk-7N53RHGS.js";
import {
require_prompts
} from "./chunk-KCF2Q4OQ.js";
import {
require_dist
} from "./chunk-IMHUIAVE.js";
import {
require_picocolors
} from "./chunk-KABHBSS3.js";
import {
__name,
__toESM
} from "./chunk-MB5KTO7X.js";
// src/core-server/utils/server-statics.ts
var import_picocolors = __toESM(require_picocolors(), 1);
var import_sirv = __toESM(require_build(), 1);
var import_ts_dedent = __toESM(require_dist(), 1);
import { existsSync, statSync } from "node:fs";
import { readFile, stat } from "node:fs/promises";
import { basename, isAbsolute, join, posix, resolve, sep, win32 } from "node:path";
import { getDirectoryFromWorkingDir, resolvePathInStorybookCache } from "storybook/internal/common";
import { logger, once } from "storybook/internal/node-logger";
var cacheDir = resolvePathInStorybookCache("", "ignored-sub").split("ignored-sub")[0];
var files = /* @__PURE__ */ new Map();
var readFileOnce = /* @__PURE__ */ __name(async (path) => {
if (files.has(path)) {
return files.get(path);
} else {
const [data, stats] = await Promise.all([readFile(path, "utf-8"), stat(path)]);
const result = { data, mtime: stats.mtimeMs };
files.set(path, result);
return result;
}
}, "readFileOnce");
var faviconWrapperPath = join(
resolvePackageDir("storybook"),
"/assets/browser/favicon-wrapper.svg"
);
var prepareNestedSvg = /* @__PURE__ */ __name((svg) => {
const [, openingTag, contents, closingTag] = svg?.match(/(<svg[^>]*>)(.*?)(<\/svg>)/s) ?? [];
if (!openingTag || !contents || !closingTag) {
return svg;
}
let width;
let height;
let modifiedTag = openingTag.replace(/width=["']([^"']*)["']/g, (_, value) => {
width = parseFloat(value);
return 'width="32px"';
}).replace(/height=["']([^"']*)["']/g, (_, value) => {
height = parseFloat(value);
return 'height="32px"';
});
const hasViewBox = /viewBox=["'][^"']*["']/.test(modifiedTag);
if (!hasViewBox && width && height) {
modifiedTag = modifiedTag.replace(/>$/, ` viewBox="0 0 ${width} ${height}">`);
}
modifiedTag = modifiedTag.replace(/preserveAspectRatio=["'][^"']*["']/g, "").replace(/>$/, ' preserveAspectRatio="xMidYMid meet">');
return modifiedTag + contents + closingTag;
}, "prepareNestedSvg");
async function useStatics(app, options) {
const staticDirs = await options.presets.apply("staticDirs") ?? [];
const faviconPath = await options.presets.apply("favicon");
const faviconDir = resolve(faviconPath, "..");
const faviconFile = basename(faviconPath);
app.use(`/${faviconFile}`, async (req, res, next) => {
const status = req.query.status;
if (status && faviconFile.endsWith(".svg") && ["active", "critical", "negative", "positive", "warning"].includes(status)) {
const [faviconInfo, faviconWrapperInfo] = await Promise.all([
readFileOnce(join(faviconDir, faviconFile)),
readFileOnce(faviconWrapperPath)
]).catch((e) => {
if (e instanceof Error) {
once.warn(`Failed to read favicon: ${e.message}`);
}
return [null, null];
});
if (faviconInfo && faviconWrapperInfo) {
const svg = faviconWrapperInfo.data.replace('<g id="mask"', `<g mask="url(#${status}-mask)"`).replace('<use id="status"', `<use href="#${status}"`).replace('<use id="icon" />', prepareNestedSvg(faviconInfo.data));
res.setHeader("Content-Type", "image/svg+xml");
res.setHeader("ETag", `"${faviconWrapperInfo.mtime}-${faviconInfo.mtime}"`);
res.end(svg);
return;
}
}
req.url = `/${faviconFile}`;
return sirvWorkaround(faviconDir)(req, res, next);
});
staticDirs.map((dir) => {
try {
const { staticDir, staticPath, targetEndpoint } = mapStaticDir(dir, options.configDir);
if (!targetEndpoint.startsWith("/sb-") && !staticDir.startsWith(cacheDir)) {
logger.info(
`=> Serving static files from ${import_picocolors.default.cyan(staticDir)} at ${import_picocolors.default.cyan(targetEndpoint)}`
);
}
if (existsSync(staticPath) && statSync(staticPath).isFile()) {
const staticPathDir = resolve(staticPath, "..");
const staticPathFile = basename(staticPath);
app.use(targetEndpoint, (req, res, next) => {
req.url = `/${staticPathFile}`;
sirvWorkaround(staticPathDir)(req, res, next);
});
} else {
app.use(targetEndpoint, sirvWorkaround(staticPath));
}
} catch (e) {
if (e instanceof Error) {
logger.warn(e.message);
}
}
});
}
__name(useStatics, "useStatics");
var sirvWorkaround = /* @__PURE__ */ __name((dir, opts = {}) => (req, res, next) => {
const originalParsedUrl = req._parsedUrl;
const maybeNext = next ? () => {
req._parsedUrl = originalParsedUrl;
next();
} : void 0;
(0, import_sirv.default)(dir, { dev: true, etag: true, extensions: [], ...opts })(req, res, maybeNext);
}, "sirvWorkaround");
var parseStaticDir = /* @__PURE__ */ __name((arg) => {
const lastColonIndex = arg.lastIndexOf(":");
const isWindowsAbsolute = win32.isAbsolute(arg);
const isWindowsRawDirOnly = isWindowsAbsolute && lastColonIndex === 1;
const splitIndex = lastColonIndex !== -1 && !isWindowsRawDirOnly ? lastColonIndex : arg.length;
const [from, to] = [arg.slice(0, splitIndex), arg.slice(splitIndex + 1)];
const staticDir = isAbsolute(from) ? from : `./${from}`;
const staticPath = resolve(staticDir);
if (!existsSync(staticPath)) {
throw new Error(
import_ts_dedent.dedent`
Failed to load static files, no such directory: ${import_picocolors.default.cyan(staticPath)}
Make sure this directory exists.
`
);
}
const targetRaw = to || (statSync(staticPath).isFile() ? basename(staticPath) : "/");
const target = targetRaw.split(sep).join(posix.sep);
const targetDir = target.replace(/^\/?/, "./");
const targetEndpoint = targetDir.substring(1);
return { staticDir, staticPath, targetDir, targetEndpoint };
}, "parseStaticDir");
var mapStaticDir = /* @__PURE__ */ __name((staticDir, configDir) => {
const specifier = typeof staticDir === "string" ? staticDir : `${staticDir.from}:${staticDir.to}`;
const normalizedDir = isAbsolute(specifier) ? specifier : getDirectoryFromWorkingDir({ configDir, workingDir: process.cwd(), directory: specifier });
return parseStaticDir(normalizedDir);
}, "mapStaticDir");
// src/core-server/withTelemetry.ts
var import_prompts = __toESM(require_prompts(), 1);
import { HandledError, cache, isCI, loadAllPresets } from "storybook/internal/common";
import { logger as logger2 } from "storybook/internal/node-logger";
import { getPrecedingUpgrade, oneWayHash, telemetry } from "storybook/internal/telemetry";
var promptCrashReports = /* @__PURE__ */ __name(async () => {
if (isCI() || !process.stdout.isTTY) {
return void 0;
}
const { enableCrashReports } = await (0, import_prompts.default)({
type: "confirm",
name: "enableCrashReports",
message: `Would you like to help improve Storybook by sending anonymous crash reports?`,
initial: true
});
await cache.set("enableCrashReports", enableCrashReports);
return enableCrashReports;
}, "promptCrashReports");
async function getErrorLevel({
cliOptions,
presetOptions,
skipPrompt
}) {
if (cliOptions.disableTelemetry) {
return "none";
}
if (!presetOptions) {
return "full";
}
const presets = await loadAllPresets(presetOptions);
const core = await presets.apply("core");
if (core?.enableCrashReports !== void 0) {
return core.enableCrashReports ? "full" : "error";
}
if (core?.disableTelemetry) {
return "none";
}
const valueFromCache = await cache.get("enableCrashReports") ?? await cache.get("enableCrashreports");
if (valueFromCache !== void 0) {
return valueFromCache ? "full" : "error";
}
if (skipPrompt) {
return "error";
}
const valueFromPrompt = await promptCrashReports();
if (valueFromPrompt !== void 0) {
return valueFromPrompt ? "full" : "error";
}
return "full";
}
__name(getErrorLevel, "getErrorLevel");
async function sendTelemetryError(_error, eventType, options) {
try {
let errorLevel = "error";
try {
errorLevel = await getErrorLevel(options);
} catch (err) {
}
if (errorLevel !== "none") {
const precedingUpgrade = await getPrecedingUpgrade();
const error = _error;
let errorHash;
if ("message" in error) {
errorHash = error.message ? oneWayHash(error.message) : "EMPTY_MESSAGE";
} else {
errorHash = "NO_MESSAGE";
}
const { code, name, category } = error;
await telemetry(
"error",
{
code,
name,
category,
eventType,
precedingUpgrade,
error: errorLevel === "full" ? error : void 0,
errorHash,
// if we ever end up sending a non-error instance, we'd like to know
isErrorInstance: error instanceof Error
},
{
immediate: true,
configDir: options.cliOptions.configDir || options.presetOptions?.configDir,
enableCrashReports: errorLevel === "full"
}
);
}
} catch (err) {
}
}
__name(sendTelemetryError, "sendTelemetryError");
async function withTelemetry(eventType, options, run) {
const enableTelemetry = !(options.cliOptions.disableTelemetry || options.cliOptions.test === true);
let canceled = false;
async function cancelTelemetry() {
canceled = true;
if (enableTelemetry) {
await telemetry("canceled", { eventType }, { stripMetadata: true, immediate: true });
}
process.exit(0);
}
__name(cancelTelemetry, "cancelTelemetry");
if (eventType === "init") {
process.on("SIGINT", cancelTelemetry);
}
if (enableTelemetry) {
telemetry("boot", { eventType }, { stripMetadata: true });
}
try {
return await run();
} catch (error) {
if (canceled) {
return void 0;
}
if (!(error instanceof HandledError)) {
const { printError = logger2.error } = options;
printError(error);
}
if (enableTelemetry) {
await sendTelemetryError(error, eventType, options);
}
throw error;
} finally {
process.off("SIGINT", cancelTelemetry);
}
}
__name(withTelemetry, "withTelemetry");
export {
useStatics,
parseStaticDir,
mapStaticDir,
getErrorLevel,
sendTelemetryError,
withTelemetry
};