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

281 lines (280 loc) • 10.7 kB
import chalk from "chalk"; import { MANIFEST_FILENAME, extractManifestSafe } from "./extractManifestAction.js"; import { defineTrace } from "@sanity/telemetry"; import { uniqBy } from "lodash-es"; import { stat, readFile } from "node:fs/promises"; import path, { resolve, join } from "node:path"; const SANITY_WORKSPACE_SCHEMA_ID_PREFIX = "_.schemas", CURRENT_WORKSPACE_SCHEMA_VERSION = "2025-05-01", GenerateManifest = defineTrace({ name: "Manifest generation executed", version: 1, description: "Manifest generation was executed" }), SchemaDeploy = defineTrace({ name: "Schema deploy action executed", version: 1, description: "Schema deploy action was executed, either via sanity schema deploy or as sanity deploy" }); function isDefined(value) { return value != null; } const createManifestReader = ({ manifestDir, output, jsonReader = parseJsonFile }) => { let parsedManifest; const parsedWorkspaces = {}, getManifest = async () => { if (parsedManifest) return parsedManifest?.parsedJson; const manifestFile = path.join(manifestDir, MANIFEST_FILENAME), result = await jsonReader(manifestFile); if (!result) throw new Error(`Manifest does not exist at ${manifestFile}. To create the manifest file, omit --no-extract-manifest or run "sanity manifest extract" first.`); return output.print(chalk.gray(`\u21B3 Read manifest from ${manifestFile} (last modified: ${result.lastModified})`)), parsedManifest = result, result.parsedJson; }; return { getManifest, getWorkspaceSchema: async (workspaceName) => { if (parsedWorkspaces[workspaceName]) return parsedWorkspaces[workspaceName]?.parsedJson; const manifest = await getManifest(); if (!manifest) throw Error("Manifest is required to read workspace schema."); const workspaceManifest = manifest.workspaces.find((workspace) => workspace.name === workspaceName); if (!workspaceManifest) throw Error(`No workspace named "${workspaceName}" found in manifest.`); const workspaceSchemaFile = path.join(manifestDir, workspaceManifest.schema ?? ""), result = await jsonReader(workspaceSchemaFile); if (!result) throw Error(`Workspace schema file at "${workspaceSchemaFile}" does not exist.`); return parsedWorkspaces[workspaceName] = result, result.parsedJson; } }; }; function resolveManifestDirectory(workDir, customPath) { const defaultOutputDir = resolve(join(workDir, "dist")), outputDir = resolve(defaultOutputDir), defaultStaticPath = join(outputDir, "static"), staticPath = customPath ?? defaultStaticPath; return path.resolve(process.cwd(), staticPath); } async function parseJsonFile(filePath) { let stats; try { stats = await stat(filePath); } catch { return; } const content = await readFile(filePath, "utf-8"), lastModified = stats.mtime.toISOString(), json = JSON.parse(content); if (!json) throw new Error(`JSON file "${filePath}" was empty.`); return { parsedJson: json, path: filePath, lastModified }; } const validForIdChars = "a-zA-Z0-9._-", validForIdPattern = new RegExp(`^[${validForIdChars}]+$`, "g"), validForNamesChars = "a-zA-Z0-9_-", validForNamesPattern = new RegExp(`^[${validForNamesChars}]+$`, "g"), requiredInId = SANITY_WORKSPACE_SCHEMA_ID_PREFIX.replace(/[.]/g, "\\."), idIdPatternString = `^${requiredInId}\\.([${validForNamesChars}]+)`, baseIdPattern = new RegExp(`${idIdPatternString}$`), taggedIdIdPattern = new RegExp(`${idIdPatternString}\\.tag\\.([${validForNamesChars}]+)$`); class FlagValidationError extends Error { constructor(message) { super(message), this.name = "FlagValidationError"; } } function parseCommonFlags(flags, context, errors) { const manifestDir = parseManifestDir(flags, errors), verbose = !!flags.verbose, extractManifest = flags["extract-manifest"] ?? !0; return { manifestDir: resolveManifestDirectory(context.workDir, manifestDir), verbose, extractManifest }; } function parseDeploySchemasConfig(flags, context) { const errors = [], commonFlags = parseCommonFlags(flags, context, errors), workspaceName = parseWorkspace(flags, errors), tag = parseTag(flags, errors), schemaRequired = !!flags["schema-required"]; return assertNoErrors(errors), { ...commonFlags, workspaceName, tag, schemaRequired }; } function parseListSchemasConfig(flags, context) { const errors = [], commonFlags = parseCommonFlags(flags, context, errors), id = parseId(flags, errors), json = !!flags.json; return assertNoErrors(errors), { ...commonFlags, json, id }; } function parseDeleteSchemasConfig(flags, context) { const errors = [], commonFlags = parseCommonFlags(flags, context, errors), ids = parseIds(flags, errors), dataset = parseDataset(flags, errors); return assertNoErrors(errors), { ...commonFlags, dataset, ids }; } function assertNoErrors(errors) { if (errors.length) throw new FlagValidationError(`Invalid arguments: ${errors.map((error) => ` - ${error}`).join(` `)}`); } function parseIds(flags, errors) { const parsedIds = parseNonEmptyString(flags, "ids", errors); if (errors.length) return []; const ids = parsedIds.split(",").map((id) => id.trim()).filter((id) => !!id).map((id) => parseWorkspaceSchemaId(id, errors)).filter(isDefined), uniqueIds = uniqBy(ids, "schemaId"); return uniqueIds.length < ids.length && errors.push("ids contains duplicates"), !errors.length && !uniqueIds.length && errors.push("ids contains no valid id strings"), uniqueIds; } function parseId(flags, errors) { const id = flags.id === void 0 ? void 0 : parseNonEmptyString(flags, "id", errors); if (id) return parseWorkspaceSchemaId(id, errors)?.schemaId; } function parseWorkspaceSchemaId(id, errors) { const trimmedId = id.trim(); if (!trimmedId.match(validForIdPattern)) { errors.push(`id can only contain characters in [${validForIdChars}] but found: "${trimmedId}"`); return; } if (trimmedId.startsWith("-")) { errors.push(`id cannot start with - (dash) but found: "${trimmedId}"`); return; } if (trimmedId.match(/\.\./g)) { errors.push(`id cannot have consecutive . (period) characters, but found: "${trimmedId}"`); return; } const [fullMatch, workspace] = trimmedId.match(taggedIdIdPattern) ?? trimmedId.match(baseIdPattern) ?? []; if (!workspace) { errors.push([`id must either match ${SANITY_WORKSPACE_SCHEMA_ID_PREFIX}.<workspaceName> `, `or ${SANITY_WORKSPACE_SCHEMA_ID_PREFIX}.<workspaceName>.tag.<tag> but found: "${trimmedId}". `, `Note that workspace name characters not in [${validForNamesChars}] has to be replaced with _ for schema id.`].join("")); return; } return { schemaId: trimmedId, workspace }; } function parseDataset(flags, errors) { return flags.dataset === void 0 ? void 0 : parseNonEmptyString(flags, "dataset", errors); } function parseWorkspace(flags, errors) { return flags.workspace === void 0 ? void 0 : parseNonEmptyString(flags, "workspace", errors); } function parseManifestDir(flags, errors) { return flags["manifest-dir"] === void 0 ? void 0 : parseNonEmptyString(flags, "manifest-dir", errors); } function parseTag(flags, errors) { if (flags.tag === void 0) return; const tag = parseNonEmptyString(flags, "tag", errors); if (!errors.length) { if (tag.includes(".")) { errors.push(`tag cannot contain . (period), but was: "${tag}"`); return; } if (!tag.match(validForNamesPattern)) { errors.push(`tag can only contain characters in [${validForNamesChars}], but was: "${tag}"`); return; } if (tag.startsWith("-")) { errors.push(`tag cannot start with - (dash) but was: "${tag}"`); return; } return tag; } } function parseNonEmptyString(flags, flagName, errors) { const flag = flags[flagName]; return !isString(flag) || !flag ? (errors.push(`${flagName} argument is empty`), "") : flag; } function isString(flag) { return typeof flag == "string"; } const SCHEMA_PERMISSION_HELP_TEXT = "For multi-project workspaces, set SANITY_AUTH_TOKEN environment variable to a token with access to the workspace projects."; async function ensureManifestExtractSatisfied(args) { const { schemaRequired, extractManifest, manifestDir, manifestExtractor, output, telemetry } = args; if (!extractManifest) return !0; const trace = telemetry.trace(GenerateManifest, { manifestDir, schemaRequired }); try { return trace.start(), await manifestExtractor(manifestDir), trace.complete(), !0; } catch (err) { if (trace.error(err), schemaRequired || err instanceof FlagValidationError) throw err; return output.print(chalk.gray(`\u21B3 Failed to extract manifest: ${err.message}`)), !1; } } function createManifestExtractor(context) { return async (manifestDir) => { const error = await extractManifestSafe({ extOptions: { path: manifestDir }, groupOrCommand: "extract", argv: [], argsWithoutOptions: [], extraArguments: [] }, context); if (!context.safe && error) throw error; }; } function createSchemaApiClient(apiClient) { const client = apiClient({ requireUser: !0, requireProject: !0 }).withConfig({ apiVersion: "v2025-03-01", useCdn: !1 }), projectId = client.config().projectId, dataset = client.config().dataset; if (!projectId) throw new Error("Project ID is not defined"); if (!dataset) throw new Error("Dataset is not defined"); return { client, projectId, dataset }; } function getProjectIdDatasetsOutString(projectIdDatasets) { return projectIdDatasets.length === 1 ? `${projectIdDatasetPair(projectIdDatasets[0])}` : `${getStringArrayOutString(projectIdDatasets.map(projectIdDatasetPair))}`; } function projectIdDatasetPair(pair) { return JSON.stringify({ projectId: pair.projectId, dataset: pair.dataset }); } function getStringArrayOutString(array) { return `[${array.map((d) => `"${d}"`).join(",")}]`; } function getStringList(array) { return array.map((s) => `- ${s}`).join(` `); } export { CURRENT_WORKSPACE_SCHEMA_VERSION, FlagValidationError, SANITY_WORKSPACE_SCHEMA_ID_PREFIX, SCHEMA_PERMISSION_HELP_TEXT, SchemaDeploy, createManifestExtractor, createManifestReader, createSchemaApiClient, ensureManifestExtractSatisfied, getProjectIdDatasetsOutString, getStringList, isDefined, parseDeleteSchemasConfig, parseDeploySchemasConfig, parseListSchemasConfig, projectIdDatasetPair, validForNamesChars, validForNamesPattern }; //# sourceMappingURL=schemaStoreOutStrings.js.map