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
129 lines (108 loc) • 4.08 kB
text/typescript
import {type Stats} from 'node:fs'
import {readFile, stat} from 'node:fs/promises'
import path, {join, resolve} from 'node:path'
import {type CliOutputter} from '@sanity/cli'
import chalk from 'chalk'
import {type CreateManifest, type ManifestSchemaType} from '../../../../manifest/manifestTypes'
import {MANIFEST_FILENAME} from '../../manifest/extractManifestAction'
import {type DeploySchemasFlags} from '../deploySchemasAction'
export type ManifestJsonReader = <T>(
filePath: string,
) => Promise<JsonFileParseSuccess<T> | undefined>
export type CreateManifestReaderFactory = (args: {
manifestDir: string
output: CliOutputter
jsonReader?: <T>(filePath: string) => Promise<JsonFileParseSuccess<T> | undefined>
}) => CreateManifestReader
export interface CreateManifestReader {
getManifest: () => Promise<CreateManifest>
getWorkspaceSchema: (workspaceName: string) => Promise<ManifestSchemaType[]>
}
interface JsonFileParseSuccess<T> {
parsedJson: T
path: string
lastModified: string
}
/**
* The manifest reader will try to read manifest and workspace schema files _once_ and cache a successful result.
* If you need to re-read the manifest from disk, create a new instance.
*/
export const createManifestReader: CreateManifestReaderFactory = ({
manifestDir,
output,
jsonReader = parseJsonFile,
}) => {
let parsedManifest: JsonFileParseSuccess<CreateManifest>
const parsedWorkspaces: Record<string, JsonFileParseSuccess<ManifestSchemaType[]> | undefined> =
{}
const getManifest: CreateManifestReader['getManifest'] = async () => {
if (parsedManifest) {
return parsedManifest?.parsedJson
}
const manifestFile = path.join(manifestDir, MANIFEST_FILENAME)
const result = await jsonReader<CreateManifest>(manifestFile)
if (!result) {
throw new Error(
`Manifest does not exist at ${manifestFile}. To create the manifest file, omit --no-${'extract-manifest' satisfies keyof DeploySchemasFlags} or run "sanity manifest extract" first.`,
)
}
output.print(
chalk.gray(`↳ Read manifest from ${manifestFile} (last modified: ${result.lastModified})`),
)
parsedManifest = result
return result.parsedJson
}
const getWorkspaceSchema: CreateManifestReader['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)
const result = await jsonReader<ManifestSchemaType[]>(workspaceSchemaFile)
if (!result) {
throw Error(`Workspace schema file at "${workspaceSchemaFile}" does not exist.`)
}
parsedWorkspaces[workspaceName] = result
return result.parsedJson
}
return {
getManifest,
getWorkspaceSchema,
}
}
export function resolveManifestDirectory(workDir: string, customPath?: string): string {
const defaultOutputDir = resolve(join(workDir, 'dist'))
const outputDir = resolve(defaultOutputDir)
const defaultStaticPath = join(outputDir, 'static')
const staticPath = customPath ?? defaultStaticPath
return path.resolve(process.cwd(), staticPath)
}
async function parseJsonFile<T>(filePath: string): Promise<JsonFileParseSuccess<T> | undefined> {
let stats: Stats
try {
stats = await stat(filePath)
} catch (err) {
// file does not exist
return undefined
}
const content = await readFile(filePath, 'utf-8')
const lastModified = stats.mtime.toISOString()
const json = JSON.parse(content) as T
if (!json) {
throw new Error(`JSON file "${filePath}" was empty.`)
}
return {
parsedJson: json,
path: filePath,
lastModified,
}
}