UNPKG

sanity

Version:

Sanity is a real-time content infrastructure with a scalable, hosted backend featuring a Graph Oriented Query Language (GROQ), asset pipelines and fast edge caches

345 lines (342 loc) • 16.4 kB
"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf, __hasOwnProp = Object.prototype.hasOwnProperty; var __copyProps = (to, from, except, desc) => { if (from && typeof from == "object" || typeof from == "function") for (let key of __getOwnPropNames(from)) !__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: !0 }) : target, mod )); var path = require("node:path"), chalk = require("chalk"), logSymbols = require("log-symbols"), semver = require("semver"), telemetry = require("@sanity/telemetry"), rimraf = require("rimraf"), previewServer = require("./previewServer.js"), runtime = require("./runtime.js"), checkRequiredDependencies = require("./checkRequiredDependencies.js"), timing = require("./timing.js"), fs = require("node:fs"), resolveFrom = require("resolve-from"), _internal = require("./_internal.js"); function _interopDefaultCompat(e) { return e && typeof e == "object" && "default" in e ? e : { default: e }; } var path__default = /* @__PURE__ */ _interopDefaultCompat(path), chalk__default = /* @__PURE__ */ _interopDefaultCompat(chalk), semver__default = /* @__PURE__ */ _interopDefaultCompat(semver), fs__default = /* @__PURE__ */ _interopDefaultCompat(fs), resolveFrom__default = /* @__PURE__ */ _interopDefaultCompat(resolveFrom); const BuildTrace = telemetry.defineTrace({ name: "Studio Build Completed", version: 0, description: "A Studio build completed" }), VENDOR_DIR = "vendor", VENDOR_IMPORTS = { react: { "^19.0.0": { ".": "./cjs/react.production.js", "./jsx-runtime": "./cjs/react-jsx-runtime.production.js", "./jsx-dev-runtime": "./cjs/react-jsx-dev-runtime.production.js", "./compiler-runtime": "./cjs/react-compiler-runtime.production.js", "./package.json": "./package.json" }, "^18.0.0": { ".": "./cjs/react.production.min.js", "./jsx-runtime": "./cjs/react-jsx-runtime.production.min.js", "./jsx-dev-runtime": "./cjs/react-jsx-dev-runtime.production.min.js", "./package.json": "./package.json" } }, "react-dom": { "^19.0.0": { ".": "./cjs/react-dom.production.js", "./client": "./cjs/react-dom-client.production.js", "./server": "./cjs/react-dom-server-legacy.browser.production.js", "./server.browser": "./cjs/react-dom-server-legacy.browser.production.js", "./static": "./cjs/react-dom-server.browser.production.js", "./static.browser": "./cjs/react-dom-server.browser.production.js", "./package.json": "./package.json" }, "^18.0.0": { ".": "./cjs/react-dom.production.min.js", "./client": "./cjs/react-dom.production.min.js", "./server": "./cjs/react-dom-server-legacy.browser.production.min.js", "./server.browser": "./cjs/react-dom-server-legacy.browser.production.min.js", "./package.json": "./package.json" } }, "styled-components": { "^6.1.0": { ".": "./dist/styled-components.esm.js", "./package.json": "./package.json" } } }; async function buildVendorDependencies({ cwd, outputDir, basePath }) { const dir = path__default.default.relative(process.cwd(), path__default.default.resolve(cwd)), entry = {}, imports = {}; for (const [packageName, ranges] of Object.entries(VENDOR_IMPORTS)) { const packageJsonPath = resolveFrom__default.default.silent(cwd, path__default.default.join(packageName, "package.json")); if (!packageJsonPath) throw new Error(`Could not find package.json for package '${packageName}' from directory '${dir}'. Is it installed?`); let packageJson; try { packageJson = JSON.parse(await fs__default.default.promises.readFile(packageJsonPath, "utf-8")); } catch (e) { const message = `Could not read package.json for package '${packageName}' from directory '${dir}'`; throw typeof e?.message == "string" ? (e.message = `${message}: ${e.message}`, e) : new Error(message, { cause: e }); } const version = semver__default.default.coerce(packageJson.version)?.version; if (!version) throw new Error(`Could not parse version '${packageJson.version}' from '${packageName}'`); const sortedRanges = Object.keys(ranges).sort((range1, range2) => { const min1 = semver__default.default.minVersion(range1), min2 = semver__default.default.minVersion(range2); if (!min1) throw new Error(`Could not parse range '${range1}'`); if (!min2) throw new Error(`Could not parse range '${range2}'`); return semver__default.default.rcompare(min1.version, min2.version); }), matchedRange = sortedRanges.find((range) => semver__default.default.satisfies(version, range)); if (!matchedRange) { const min = semver__default.default.minVersion(sortedRanges[sortedRanges.length - 1]); throw min ? semver__default.default.gt(min.version, version) ? new Error(`Package '${packageName}' requires at least ${min.version}.`) : new Error(`Version '${version}' of package '${packageName}' is not supported yet.`) : new Error(`Could not find a minimum version for package '${packageName}'`); } const subpaths = ranges[matchedRange]; for (const [subpath, relativeEntryPoint] of Object.entries(subpaths)) { const packagePath = path__default.default.dirname(packageJsonPath), entryPoint = resolveFrom__default.default.silent(packagePath, relativeEntryPoint); if (!entryPoint) throw new Error(`Failed to resolve entry point '${path__default.default.join(packageName, relativeEntryPoint)}'. `); const specifier = path__default.default.posix.join(packageName, subpath), chunkName = path__default.default.posix.join(packageName, path__default.default.relative(packageName, specifier) || "index"); entry[chunkName] = entryPoint, imports[specifier] = path__default.default.posix.join("/", basePath, VENDOR_DIR, `${chunkName}.mjs`); } } const { build } = await import("vite"); let buildResult = await build({ // Define a custom cache directory so that sanity's vite cache // does not conflict with any potential local vite projects cacheDir: "node_modules/.sanity/vite-vendor", root: cwd, configFile: !1, logLevel: "silent", appType: "custom", mode: "production", define: { "process.env.NODE_ENV": JSON.stringify("production") }, build: { commonjsOptions: { strictRequires: "auto" }, minify: !0, emptyOutDir: !1, // Rely on CLI to do this outDir: path__default.default.join(outputDir, VENDOR_DIR), lib: { entry, formats: ["es"] }, rollupOptions: { external: runtime.createExternalFromImportMap({ imports }), output: { entryFileNames: "[name]-[hash].mjs", chunkFileNames: "[name]-[hash].mjs", exports: "named", format: "es" }, treeshake: { preset: "recommended" } } } }); buildResult = Array.isArray(buildResult) ? buildResult : [buildResult]; const hashedImports = {}, output = buildResult.flatMap((i) => i.output); for (const chunk of output) if (chunk.type !== "asset") for (const [specifier, originalPath] of Object.entries(imports)) originalPath.endsWith(`${chunk.name}.mjs`) && (hashedImports[specifier] = path__default.default.posix.join("/", basePath, VENDOR_DIR, chunk.fileName)); return hashedImports; } async function getRemoteResolvedVersion(fetchFn, url) { try { return (await fetchFn(url, { method: "HEAD", redirect: "manual" })).headers.get("x-resolved-version"); } catch (err) { throw new Error(`Failed to fetch remote version for ${url}: ${err.message}`); } } async function compareStudioDependencyVersions(autoUpdatesImports, workDir, fetchFn = globalThis.fetch) { const manifest = checkRequiredDependencies.readPackageJson(path__default.default.join(workDir, "package.json")), dependencies = { ...manifest.dependencies, ...manifest.devDependencies }, failedDependencies = [], filteredAutoUpdatesImports = Object.entries(autoUpdatesImports).filter(([pkg]) => !pkg.endsWith("/")); for (const [pkg, value] of filteredAutoUpdatesImports) { const resolvedVersion = await getRemoteResolvedVersion(fetchFn, value); if (!resolvedVersion) throw new Error(`Failed to fetch remote version for ${value}`); const dependency = dependencies[pkg], manifestPath = resolveFrom__default.default.silent(workDir, path__default.default.join(pkg, "package.json")), installed = semver__default.default.coerce(manifestPath ? checkRequiredDependencies.readPackageJson(manifestPath).version : dependency); if (!installed) throw new Error(`Failed to parse installed version for ${pkg}`); semver__default.default.eq(resolvedVersion, installed.version) || failedDependencies.push({ pkg, installed: installed.version, remote: resolvedVersion }); } return failedDependencies; } const MODULES_HOST = process.env.SANITY_INTERNAL_ENV === "staging" ? "https://sanity-cdn.work" : "https://sanity-cdn.com"; function getAutoUpdateImportMap(version) { const timestamp = `t${Math.floor(Date.now() / 1e3)}`; return { sanity: `${MODULES_HOST}/v1/modules/sanity/default/${version}/${timestamp}`, "sanity/": `${MODULES_HOST}/v1/modules/sanity/default/${version}/${timestamp}/`, "@sanity/vision": `${MODULES_HOST}/v1/modules/@sanity__vision/default/${version}/${timestamp}`, "@sanity/vision/": `${MODULES_HOST}/v1/modules/@sanity__vision/default/${version}/${timestamp}/` }; } function shouldAutoUpdate({ flags, cliConfig }) { return "auto-updates" in flags ? !!flags["auto-updates"] : cliConfig && "autoUpdates" in cliConfig ? !!cliConfig.autoUpdates : !1; } async function buildSanityStudio(args, context, overrides) { const timer = timing.getTimer(), { output, prompt, workDir, cliConfig, telemetry: telemetry$1 = telemetry.noopLogger } = context, flags = { minify: !0, stats: !1, "source-maps": !1, ...args.extOptions }, unattendedMode = !!(flags.yes || flags.y), defaultOutputDir = path__default.default.resolve(path__default.default.join(workDir, "dist")), outputDir = path__default.default.resolve(args.argsWithoutOptions[0] || defaultOutputDir), isApp = _internal.determineIsApp(cliConfig); await checkRequiredDependencies.checkStudioDependencyVersions(workDir); const { didInstall, installedSanityVersion } = await checkRequiredDependencies.checkRequiredDependencies(context); if (didInstall) return { didCompile: !1 }; const autoUpdatesEnabled = shouldAutoUpdate({ flags, cliConfig }), coercedSanityVersion = semver__default.default.coerce(installedSanityVersion)?.version; if (autoUpdatesEnabled && !coercedSanityVersion) throw new Error(`Failed to parse installed Sanity version: ${installedSanityVersion}`); const version = encodeURIComponent(`^${coercedSanityVersion}`), autoUpdatesImports = getAutoUpdateImportMap(version); if (autoUpdatesEnabled) { output.print(`${logSymbols.info} Building with auto-updates enabled`); try { const result = await compareStudioDependencyVersions(autoUpdatesImports, workDir); if (result?.length && !unattendedMode && !await prompt.single({ type: "confirm", message: chalk__default.default.yellow(`The following local package versions are different from the versions currently served at runtime. When using auto updates, we recommend that you test locally with the same versions before deploying. ${result.map((mod) => ` - ${mod.pkg} (local version: ${mod.installed}, runtime version: ${mod.remote})`).join(` `)} Continue anyway?`), default: !1 })) return process.exit(0); } catch (err) { throw err; } } const envVarKeys = getSanityEnvVars(); envVarKeys.length > 0 && (output.print(` Including the following environment variables as part of the JavaScript bundle:`), envVarKeys.forEach((key) => output.print(`- ${key}`)), output.print("")); let shouldClean = !0; outputDir !== defaultOutputDir && !unattendedMode && (shouldClean = await prompt.single({ type: "confirm", message: `Do you want to delete the existing directory (${outputDir}) first?`, default: !0 })); let basePath = "/"; const envBasePath = process.env.SANITY_STUDIO_BASEPATH, configBasePath = cliConfig?.project?.basePath; overrides?.basePath ? basePath = overrides.basePath : envBasePath ? basePath = envBasePath : configBasePath && (basePath = configBasePath), envBasePath && configBasePath && output.warn(`Overriding configured base path (${configBasePath}) with value from environment variable (${envBasePath})`); let spin; if (shouldClean) { timer.start("cleanOutputFolder"), spin = output.spinner("Clean output folder").start(), await rimraf.rimraf(outputDir); const cleanDuration = timer.end("cleanOutputFolder"); spin.text = `Clean output folder (${cleanDuration.toFixed()}ms)`, spin.succeed(); } spin = output.spinner(`Build Sanity ${isApp ? "application" : "Studio"}`).start(); const trace = telemetry$1.trace(BuildTrace); trace.start(); let importMap; autoUpdatesEnabled && (importMap = { imports: { ...await buildVendorDependencies({ cwd: workDir, outputDir, basePath }), ...autoUpdatesImports } }); try { timer.start("bundleStudio"); const bundle = await previewServer.buildStaticFiles({ cwd: workDir, outputDir, basePath, sourceMap: !!flags["source-maps"], minify: !!flags.minify, vite: cliConfig && "vite" in cliConfig ? cliConfig.vite : void 0, importMap, reactCompiler: cliConfig && "reactCompiler" in cliConfig ? cliConfig.reactCompiler : void 0, entry: cliConfig && "app" in cliConfig ? cliConfig.app?.entry : void 0, isApp }); trace.log({ outputSize: bundle.chunks.flatMap((chunk) => chunk.modules.flatMap((mod) => mod.renderedLength)).reduce((sum, n) => sum + n, 0) }); const buildDuration = timer.end("bundleStudio"); spin.text = `Build Sanity ${isApp ? "application" : "Studio"} (${buildDuration.toFixed()}ms)`, spin.succeed(), trace.complete(), flags.stats && (output.print(` Largest module files:`), output.print(formatModuleSizes(sortModulesBySize(bundle.chunks).slice(0, 15)))); } catch (err) { throw spin.fail(), trace.error(err), err; } return { didCompile: !0 }; } function getSanityEnvVars(env = process.env) { return Object.keys(env).filter((key) => key.toUpperCase().startsWith("SANITY_STUDIO_")); } function sortModulesBySize(chunks) { return chunks.flatMap((chunk) => chunk.modules).sort((modA, modB) => modB.renderedLength - modA.renderedLength); } function formatModuleSizes(modules) { const lines = []; for (const mod of modules) lines.push(` - ${formatModuleName(mod.name)} (${formatSize(mod.renderedLength)})`); return lines.join(` `); } function formatModuleName(modName) { const delimiter = "/node_modules/", nodeIndex = modName.lastIndexOf(delimiter); return nodeIndex === -1 ? modName : modName.slice(nodeIndex + delimiter.length); } function formatSize(bytes) { return chalk__default.default.cyan(`${(bytes / 1024).toFixed()} kB`); } var buildAction = /* @__PURE__ */ Object.freeze({ __proto__: null, default: buildSanityStudio }); exports.buildAction = buildAction; exports.buildSanityStudio = buildSanityStudio; exports.shouldAutoUpdate = shouldAutoUpdate; //# sourceMappingURL=buildAction.js.map