@sanity/import
Version:
Import documents to a Sanity dataset
117 lines (97 loc) • 3.75 kB
JavaScript
const {generateHelpUrl} = require('@sanity/generate-help-url')
const debug = require('debug')('sanity:import:asset-validation')
const pMap = require('p-map')
const urlExists = require('./util/urlExists')
const DEFAULT_VERIFY_CONCURRENCY = 12
const REQUIRED_PROPERTIES = {
_id: 'string',
_type: 'string',
assetId: 'string',
extension: 'string',
mimeType: 'string',
path: 'string',
sha1hash: 'string',
size: 'number',
url: 'string',
}
module.exports = async function validateAssetDocuments(docs, options) {
const {targetProjectId, targetDataset} = options
const concurrency = options.assetVerificationConcurrency || DEFAULT_VERIFY_CONCURRENCY
const assetDocs = docs.filter((doc) => /^sanity\.[a-zA-Z]+Asset$/.test(doc._type || ''))
if (assetDocs.length === 0) {
return
}
options.onProgress({step: 'Validating asset documents'})
assetDocs.forEach((doc) => validateAssetDocumentProperties(doc))
// Don't allow assets that reference different datasets (unless explicitly allowing it)
if (!options.allowAssetsInDifferentDataset) {
assetDocs.forEach((doc) => {
const id = doc._id || doc.url
const {projectId, dataset} = getLocationFromDocument(doc)
const resolveText = `See ${generateHelpUrl('import-asset-has-different-target')}`
if (projectId !== targetProjectId) {
throw new Error(
`Asset ${id} references a different project ID than the specified target (asset is in ${projectId}, importing to ${targetProjectId}). ${resolveText}`,
)
}
if (dataset !== targetDataset) {
throw new Error(
`Asset ${id} references a different dataset than the specified target (asset is in ${dataset}, importing to ${targetDataset}). ${resolveText}`,
)
}
})
}
if (!options.allowFailingAssets) {
await pMap(assetDocs, ensureAssetUrlExists, {concurrency})
}
}
function getLocationFromDocument(doc) {
const url = doc.path || doc.url || ''
const path = url.replace(/^https:\/\/cdn\.sanity\.[a-z]+\//, '')
const [, projectId, dataset] = path.split('/')
return {projectId, dataset}
}
async function ensureAssetUrlExists(assetDoc) {
const url = assetDoc.url
const start = Date.now()
const exists = await urlExists(url)
debug(`${url}: %s (%d ms)`, exists ? 'exists' : 'does not exist', Date.now() - start)
if (!exists) {
const helpUrl = generateHelpUrl('import-asset-file-does-not-exist')
throw new Error(
`Document ${assetDoc._id} points to a URL that does not exist (${url}). See ${helpUrl}.`,
)
}
return true
}
function validateAssetDocumentProperties(assetDoc) {
Object.keys(REQUIRED_PROPERTIES).forEach((prop) => {
const expectedType = REQUIRED_PROPERTIES[prop]
if (typeof assetDoc[prop] !== expectedType) {
const errorType =
typeof assetDoc[prop] === 'undefined' ? 'is missing' : 'has invalid type for'
throw new Error(`Asset document ${assetDoc._id} ${errorType} required property "${prop}"`)
}
})
if (assetDoc._type === 'sanity.imageAsset') {
validateImageMetadata(assetDoc)
}
}
function validateImageMetadata(assetDoc) {
if (!assetDoc.metadata) {
throw new Error(`Asset document ${assetDoc._id} is missing required property "metadata"`)
}
if (!assetDoc.metadata.dimensions) {
throw new Error(
`Asset document ${assetDoc._id} is missing required property "metadata.dimensions"`,
)
}
const dimensionProps = ['width', 'height', 'aspectRatio']
dimensionProps.forEach((prop) => {
if (typeof assetDoc.metadata.dimensions[prop] !== 'number') {
throw new Error(
`Asset document ${assetDoc._id} is missing required property "metadata.dimensions.${prop}"`,
)
}
})
}