@sanity/import
Version:
Import documents to a Sanity dataset
105 lines (104 loc) • 4.36 kB
JavaScript
import { generateHelpUrl } from '@sanity/generate-help-url';
import debug from 'debug';
import pMap from 'p-map';
import { getAssetUrlStatus } from './util/urlExists.js';
const logger = debug('sanity:import:asset-validation');
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'
};
export async function validateAssetDocuments(docs, options) {
const { targetDataset, targetProjectId } = 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'
});
for (const doc of assetDocs)validateAssetDocumentProperties(doc);
// Don't allow assets that reference different datasets (unless explicitly allowing it)
if (!options.allowAssetsInDifferentDataset) {
for (const doc of assetDocs){
const id = doc._id || doc.url;
const { dataset, projectId } = 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 {
dataset: dataset || '',
projectId: projectId || ''
};
}
async function ensureAssetUrlExists(assetDoc) {
const url = assetDoc.url;
const start = Date.now();
const status = await getAssetUrlStatus(url);
logger(`${url}: %d (%d ms)`, status, Date.now() - start);
if (status === 200) {
return true;
}
if (status !== 404) {
throw new Error(`Document ${assetDoc._id} points to a URL that could not be verified (${url}): ` + `server returned HTTP ${status}. ` + `Re-run with --allow-failing-assets to skip URL verification.`);
}
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}.`);
}
function validateAssetDocumentProperties(assetDoc) {
for (const prop of Object.keys(REQUIRED_PROPERTIES)){
const expectedType = REQUIRED_PROPERTIES[prop];
const propValue = assetDoc[prop];
if (typeof propValue !== expectedType) {
const errorType = propValue === 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'
];
const metadata = assetDoc.metadata;
for (const prop of dimensionProps){
if (typeof metadata.dimensions?.[prop] !== 'number') {
throw new TypeError(`Asset document ${assetDoc._id} is missing required property "metadata.dimensions.${prop}"`);
}
}
}
//# sourceMappingURL=validateAssetDocuments.js.map