Socket
Socket
Sign inDemoInstall

@sanity/export

Package Overview
Dependencies
68
Maintainers
7
Versions
1038
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 0.126.0 to 0.126.2

199

lib/AssetHandler.js

@@ -8,2 +8,3 @@ 'use strict';

const path = require('path');
const fse = require('fs-extra');
const miss = require('mississippi');

@@ -17,25 +18,39 @@ const PQueue = require('p-queue');

const ACTION_REWRITE = 'rewrite';
const precompressedExts = ['.zip', '.gz', '.rar', '.png', '.jpeg', '.jpg', '.gif'];
class AssetHandler {
constructor(options) {
this.rewriteAssets = miss.through.obj((doc, enc, callback) => {
if (['sanity.imageAsset', 'sanity.fileAsset'].includes(doc._type)) {
const type = doc._type === 'sanity.imageAsset' ? 'image' : 'file';
const filePath = `${type}s/${generateFilename(doc._id)}`;
this.queueAssetDownload(doc, filePath);
callback();
return;
}
var _this = this;
callback(null, this.findAndModify(doc, ACTION_REWRITE));
});
this.stripAssets = miss.through.obj((doc, enc, callback) => {
if (['sanity.imageAsset', 'sanity.fileAsset'].includes(doc._type)) {
callback();
return;
}
this.rewriteAssets = miss.through.obj((() => {
var _ref = _asyncToGenerator(function* (doc, enc, callback) {
if (['sanity.imageAsset', 'sanity.fileAsset'].includes(doc._type)) {
const type = doc._type === 'sanity.imageAsset' ? 'image' : 'file';
const filePath = `${type}s/${generateFilename(doc._id)}`;
_this.assetsSeen.set(doc._id, type);
_this.queueAssetDownload(doc, filePath);
callback();
return;
}
callback(null, this.findAndModify(doc, ACTION_REMOVE));
});
callback(null, (yield _this.findAndModify(doc, ACTION_REWRITE)));
});
return function (_x, _x2, _x3) {
return _ref.apply(this, arguments);
};
})());
this.stripAssets = miss.through.obj((() => {
var _ref2 = _asyncToGenerator(function* (doc, enc, callback) {
if (['sanity.imageAsset', 'sanity.fileAsset'].includes(doc._type)) {
callback();
return;
}
callback(null, (yield _this.findAndModify(doc, ACTION_REMOVE)));
});
return function (_x4, _x5, _x6) {
return _ref2.apply(this, arguments);
};
})());
this.skipAssets = miss.through.obj((doc, enc, callback) => {

@@ -52,41 +67,73 @@ const isAsset = ['sanity.imageAsset', 'sanity.fileAsset'].includes(doc._type);

this.findAndModify = (item, action) => {
if (Array.isArray(item)) {
return item.map(child => this.findAndModify(child, action)).filter(Boolean);
}
this.findAndModify = (() => {
var _ref3 = _asyncToGenerator(function* (item, action) {
if (Array.isArray(item)) {
const children = yield Promise.all(item.map(function (child) {
return _this.findAndModify(child, action);
}));
return children.filter(Boolean);
}
if (!item || typeof item !== 'object') {
return item;
}
if (!item || typeof item !== 'object') {
return item;
}
const assetType = getAssetType(item);
if (assetType) {
if (action === ACTION_REMOVE) {
const isAsset = isAssetField(item);
if (isAsset && action === ACTION_REMOVE) {
return undefined;
}
if (action === ACTION_REWRITE) {
const filePath = `${assetType}s/${generateFilename(item.asset._ref)}`;
return {
_sanityAsset: `${assetType}@file://./${filePath}`
};
if (isAsset && action === ACTION_REWRITE) {
const assetId = item.asset._ref;
if (isModernAsset(assetId)) {
const assetType = getAssetType(item);
const filePath = `${assetType}s/${generateFilename(assetId)}`;
return { _sanityAsset: `${assetType}@file://./${filePath}` };
}
// Legacy asset
const type = _this.assetsSeen.get(assetId) || (yield _this.lookupAssetType(assetId));
const filePath = `${type}s/${generateFilename(assetId)}`;
return { _sanityAsset: `${type}@file://./${filePath}` };
}
}
return Object.keys(item).reduce((acc, key) => {
const value = item[key];
acc[key] = this.findAndModify(value, action);
const newItem = {};
const keys = Object.keys(item);
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
const value = item[key];
if (typeof acc[key] === 'undefined') {
delete acc[key];
// eslint-disable-next-line no-await-in-loop
newItem[key] = yield _this.findAndModify(value, action);
if (typeof newItem[key] === 'undefined') {
delete newItem[key];
}
}
return acc;
}, {});
};
return newItem;
});
return function (_x7, _x8) {
return _ref3.apply(this, arguments);
};
})();
this.lookupAssetType = (() => {
var _ref4 = _asyncToGenerator(function* (assetId) {
const docType = yield _this.client.fetch('*[_id == $id][0]._type', { id: assetId });
return docType === 'sanity.imageAsset' ? 'image' : 'file';
});
return function (_x9) {
return _ref4.apply(this, arguments);
};
})();
this.client = options.client;
this.archive = options.archive;
this.archivePrefix = options.prefix;
this.tmpDir = options.tmpDir;
this.assetDirsCreated = false;
this.assetsSeen = new Map();
this.filesWritten = 0;
this.queueSize = 0;

@@ -100,2 +147,3 @@ this.queue = options.queue || new PQueue({ concurrency: 3 });

clear() {
this.assetsSeen.clear();
this.queue.clear();

@@ -125,2 +173,7 @@ this.queueSize = 0;

queueAssetDownload(assetDoc, dstPath) {
if (!assetDoc.url) {
debug('Asset document "%s" does not have a URL property, skipping', assetDoc._id);
return;
}
debug('Adding download task for %s (destination: %s)', assetDoc._id, dstPath);

@@ -132,3 +185,3 @@ this.queueSize++;

downloadAsset(url, dstPath) {
var _this = this;
var _this2 = this;

@@ -138,22 +191,31 @@ return _asyncToGenerator(function* () {

const stream = yield requestStream({ url, headers });
const store = precompressedExts.includes(path.extname(dstPath));
if (stream.statusCode !== 200) {
_this.archive.abort();
_this.queue.clear();
_this.reject(new Error(`Referenced asset URL "${url}" returned HTTP ${stream.statusCode}`));
_this2.queue.clear();
_this2.reject(new Error(`Referenced asset URL "${url}" returned HTTP ${stream.statusCode}`));
return;
}
debug('Asset stream ready, appending to archive at %s', dstPath);
_this.archive.append(stream, {
name: path.basename(dstPath),
prefix: [_this.archivePrefix, path.dirname(dstPath)].join('/'),
store
});
if (!_this2.assetDirsCreated) {
/* eslint-disable no-sync */
fse.ensureDirSync(path.join(_this2.tmpDir, 'files'));
fse.ensureDirSync(path.join(_this2.tmpDir, 'images'));
/* eslint-enable no-sync */
_this2.assetDirsCreated = true;
}
debug('Asset stream ready, writing to filesystem at %s', dstPath);
yield writeStream(path.join(_this2.tmpDir, dstPath), stream);
_this2.filesWritten++;
})();
}
// eslint-disable-next-line complexity
}
function isAssetField(item) {
return item.asset && item.asset._ref;
}
function getAssetType(item) {

@@ -164,6 +226,6 @@ if (!item.asset || typeof item.asset._ref !== 'string') {

var _ref = item.asset._ref.match(/^(image|file)-/) || [],
_ref2 = _slicedToArray(_ref, 2);
var _ref5 = item.asset._ref.match(/^(image|file)-/) || [],
_ref6 = _slicedToArray(_ref5, 2);
const type = _ref2[1];
const type = _ref6[1];

@@ -173,17 +235,24 @@ return type || null;

function isModernAsset(assetId) {
return (/^(image|file)/.test(assetId)
);
}
function generateFilename(assetId) {
var _assetId$match = assetId.match(/^(image|file)-(.*?)(-[a-z]+)?$/),
_assetId$match2 = _slicedToArray(_assetId$match, 4);
var _ref7 = assetId.match(/^(image|file)-(.*?)(-[a-z]+)?$/) || [],
_ref8 = _slicedToArray(_ref7, 4);
const asset = _assetId$match2[2],
ext = _assetId$match2[3];
const asset = _ref8[2],
ext = _ref8[3];
const extension = (ext || 'bin').replace(/^-/, '');
return `${asset}.${extension}`;
return asset ? `${asset}.${extension}` : `${assetId}.bin`;
}
function lookupAssetUrl(client, assetId) {
return client.fetch('*[_id == $id][0].url', { id: assetId });
function writeStream(filePath, stream) {
return new Promise((resolve, reject) => miss.pipe(stream, fse.createWriteStream(filePath), err => {
return err ? reject(err) : resolve();
}));
}
module.exports = AssetHandler;

@@ -5,4 +5,6 @@ 'use strict';

const fs = require('fs');
const os = require('os');
const path = require('path');
const zlib = require('zlib');
const fse = require('fs-extra');
const miss = require('mississippi');

@@ -34,6 +36,12 @@ const split = require('split2');

const prefix = `${opts.dataset}-export-${slugDate}`;
const assetHandler = new AssetHandler({ client: options.client, archive, prefix });
const tmpDir = path.join(os.tmpdir(), prefix);
const assetHandler = new AssetHandler({
client: options.client,
tmpDir,
prefix
});
debug('Outputting assets (temporarily) to %s', tmpDir);
debug('Outputting to %s', options.outputPath === '-' ? 'stdout' : options.outputPath);
const outputStream = options.outputPath === '-' ? process.stdout : fs.createWriteStream(options.outputPath);
const outputStream = options.outputPath === '-' ? process.stdout : fse.createWriteStream(options.outputPath);

@@ -47,2 +55,22 @@ let assetStreamHandler = assetHandler.noop;

var _ref = _asyncToGenerator(function* (resolve, reject) {
let onComplete = (() => {
var _ref3 = _asyncToGenerator(function* (err) {
onProgress({ step: 'Clearing temporary files...' });
yield fse.remove(tmpDir);
if (!err) {
resolve();
return;
}
debug('Error during streaming: %s', err.stack);
assetHandler.clear();
reject(err);
});
return function onComplete(_x4) {
return _ref3.apply(this, arguments);
};
})();
miss.finished(archive, function (archiveErr) {

@@ -56,3 +84,2 @@ if (archiveErr) {

debug('Archive finished!');
resolve();
});

@@ -62,4 +89,25 @@

onProgress({ step: 'Exporting documents...' });
let documentCount = 0;
let lastReported = Date.now();
const reportDocumentCount = function reportDocumentCount(chunk, enc, cb) {
++documentCount;
const now = Date.now();
if (now - lastReported > 50) {
onProgress({
step: 'Exporting documents...',
current: documentCount,
total: '?',
update: true
});
lastReported = now;
}
cb(null, chunk);
};
const inputStream = yield getDocumentsStream(options.client, options.dataset);
const jsonStream = miss.pipeline(inputStream, split(JSON.parse), rejectOnApiError, filterSystemDocuments, assetStreamHandler, filterDocumentTypes(options.types), options.drafts ? miss.through.obj() : filterDrafts, stringifyStream);
const jsonStream = miss.pipeline(inputStream, split(JSON.parse), rejectOnApiError, filterSystemDocuments, assetStreamHandler, filterDocumentTypes(options.types), options.drafts ? miss.through.obj() : filterDrafts, stringifyStream, miss.through(reportDocumentCount));

@@ -72,14 +120,28 @@ miss.finished(jsonStream, (() => {

onProgress({
step: 'Exporting documents...',
current: documentCount,
total: documentCount,
update: true
});
if (!options.raw && options.assets) {
onProgress({ step: 'Downloading assets...' });
}
archive.on('progress', function ({ entries }) {
onProgress({
step: 'Downloading assets...',
current: entries.processed,
total: Math.max(assetHandler.queueSize, entries.processed),
update: true
});
let prevCompleted = 0;
const progressInterval = setInterval(function () {
const completed = assetHandler.queueSize - assetHandler.queue.size;
if (prevCompleted === completed) {
return;
}
prevCompleted = completed;
onProgress({
step: 'Downloading assets...',
current: completed,
total: assetHandler.queueSize,
update: true
});
}
}, 500);

@@ -89,3 +151,5 @@ debug('Waiting for asset handler to complete downloads');

yield assetHandler.finish();
clearInterval(progressInterval);
} catch (assetErr) {
clearInterval(progressInterval);
reject(assetErr);

@@ -95,3 +159,8 @@ return;

// Add all downloaded assets to archive
archive.directory(path.join(tmpDir, 'files'), `${prefix}/files`, { store: true });
archive.directory(path.join(tmpDir, 'images'), `${prefix}/images`, { store: true });
debug('Finalizing archive, flushing streams');
onProgress({ step: 'Adding assets to archive...' });
archive.finalize();

@@ -105,14 +174,8 @@ });

archive.on('warning', function (err) {
debug('Archive warning: %s', err.message);
});
archive.append(jsonStream, { name: 'data.ndjson', prefix });
miss.pipe(archive, outputStream, onComplete);
function onComplete(err) {
if (!err) {
return;
}
debug('Error during streaming: %s', err.stack);
assetHandler.clear();
reject(err);
}
});

@@ -119,0 +182,0 @@

{
"name": "@sanity/export",
"version": "0.126.0",
"version": "0.126.2",
"description": "Export Sanity documents and assets",

@@ -27,2 +27,3 @@ "main": "lib/export.js",

"debug": "^3.1.0",
"fs-extra": "^5.0.0",
"lodash": "^4.17.4",

@@ -29,0 +30,0 @@ "mississippi": "^2.0.0",

const path = require('path')
const fse = require('fs-extra')
const miss = require('mississippi')

@@ -10,3 +11,2 @@ const PQueue = require('p-queue')

const ACTION_REWRITE = 'rewrite'
const precompressedExts = ['.zip', '.gz', '.rar', '.png', '.jpeg', '.jpg', '.gif']

@@ -16,5 +16,7 @@ class AssetHandler {

this.client = options.client
this.archive = options.archive
this.archivePrefix = options.prefix
this.tmpDir = options.tmpDir
this.assetDirsCreated = false
this.assetsSeen = new Map()
this.filesWritten = 0
this.queueSize = 0

@@ -28,2 +30,3 @@ this.queue = options.queue || new PQueue({concurrency: 3})

clear() {
this.assetsSeen.clear()
this.queue.clear()

@@ -42,6 +45,7 @@ this.queueSize = 0

// placeholder asset references (_sanityAsset: 'image@file:///local/path')
rewriteAssets = miss.through.obj((doc, enc, callback) => {
rewriteAssets = miss.through.obj(async (doc, enc, callback) => {
if (['sanity.imageAsset', 'sanity.fileAsset'].includes(doc._type)) {
const type = doc._type === 'sanity.imageAsset' ? 'image' : 'file'
const filePath = `${type}s/${generateFilename(doc._id)}`
this.assetsSeen.set(doc._id, type)
this.queueAssetDownload(doc, filePath)

@@ -52,3 +56,3 @@ callback()

callback(null, this.findAndModify(doc, ACTION_REWRITE))
callback(null, await this.findAndModify(doc, ACTION_REWRITE))
})

@@ -58,3 +62,3 @@

// as well as references to assets (*.asset._ref ^= (image|file)-)
stripAssets = miss.through.obj((doc, enc, callback) => {
stripAssets = miss.through.obj(async (doc, enc, callback) => {
if (['sanity.imageAsset', 'sanity.fileAsset'].includes(doc._type)) {

@@ -65,3 +69,3 @@ callback()

callback(null, this.findAndModify(doc, ACTION_REMOVE))
callback(null, await this.findAndModify(doc, ACTION_REMOVE))
})

@@ -84,2 +88,7 @@

queueAssetDownload(assetDoc, dstPath) {
if (!assetDoc.url) {
debug('Asset document "%s" does not have a URL property, skipping', assetDoc._id)
return
}
debug('Adding download task for %s (destination: %s)', assetDoc._id, dstPath)

@@ -93,6 +102,4 @@ this.queueSize++

const stream = await requestStream({url, headers})
const store = precompressedExts.includes(path.extname(dstPath))
if (stream.statusCode !== 200) {
this.archive.abort()
this.queue.clear()

@@ -103,13 +110,21 @@ this.reject(new Error(`Referenced asset URL "${url}" returned HTTP ${stream.statusCode}`))

debug('Asset stream ready, appending to archive at %s', dstPath)
this.archive.append(stream, {
name: path.basename(dstPath),
prefix: [this.archivePrefix, path.dirname(dstPath)].join('/'),
store
})
if (!this.assetDirsCreated) {
/* eslint-disable no-sync */
fse.ensureDirSync(path.join(this.tmpDir, 'files'))
fse.ensureDirSync(path.join(this.tmpDir, 'images'))
/* eslint-enable no-sync */
this.assetDirsCreated = true
}
debug('Asset stream ready, writing to filesystem at %s', dstPath)
await writeStream(path.join(this.tmpDir, dstPath), stream)
this.filesWritten++
}
findAndModify = (item, action) => {
// eslint-disable-next-line complexity
findAndModify = async (item, action) => {
if (Array.isArray(item)) {
return item.map(child => this.findAndModify(child, action)).filter(Boolean)
const children = await Promise.all(item.map(child => this.findAndModify(child, action)))
return children.filter(Boolean)
}

@@ -121,29 +136,48 @@

const assetType = getAssetType(item)
if (assetType) {
if (action === ACTION_REMOVE) {
return undefined
const isAsset = isAssetField(item)
if (isAsset && action === ACTION_REMOVE) {
return undefined
}
if (isAsset && action === ACTION_REWRITE) {
const assetId = item.asset._ref
if (isModernAsset(assetId)) {
const assetType = getAssetType(item)
const filePath = `${assetType}s/${generateFilename(assetId)}`
return {_sanityAsset: `${assetType}@file://./${filePath}`}
}
if (action === ACTION_REWRITE) {
const filePath = `${assetType}s/${generateFilename(item.asset._ref)}`
return {
_sanityAsset: `${assetType}@file://./${filePath}`
}
}
// Legacy asset
const type = this.assetsSeen.get(assetId) || (await this.lookupAssetType(assetId))
const filePath = `${type}s/${generateFilename(assetId)}`
return {_sanityAsset: `${type}@file://./${filePath}`}
}
return Object.keys(item).reduce((acc, key) => {
const newItem = {}
const keys = Object.keys(item)
for (let i = 0; i < keys.length; i++) {
const key = keys[i]
const value = item[key]
acc[key] = this.findAndModify(value, action)
if (typeof acc[key] === 'undefined') {
delete acc[key]
// eslint-disable-next-line no-await-in-loop
newItem[key] = await this.findAndModify(value, action)
if (typeof newItem[key] === 'undefined') {
delete newItem[key]
}
}
return acc
}, {})
return newItem
}
lookupAssetType = async assetId => {
const docType = await this.client.fetch('*[_id == $id][0]._type', {id: assetId})
return docType === 'sanity.imageAsset' ? 'image' : 'file'
}
}
function isAssetField(item) {
return item.asset && item.asset._ref
}
function getAssetType(item) {

@@ -158,12 +192,20 @@ if (!item.asset || typeof item.asset._ref !== 'string') {

function isModernAsset(assetId) {
return /^(image|file)/.test(assetId)
}
function generateFilename(assetId) {
const [, , asset, ext] = assetId.match(/^(image|file)-(.*?)(-[a-z]+)?$/)
const [, , asset, ext] = assetId.match(/^(image|file)-(.*?)(-[a-z]+)?$/) || []
const extension = (ext || 'bin').replace(/^-/, '')
return `${asset}.${extension}`
return asset ? `${asset}.${extension}` : `${assetId}.bin`
}
function lookupAssetUrl(client, assetId) {
return client.fetch('*[_id == $id][0].url', {id: assetId})
function writeStream(filePath, stream) {
return new Promise((resolve, reject) =>
miss.pipe(stream, fse.createWriteStream(filePath), err => {
return err ? reject(err) : resolve()
})
)
}
module.exports = AssetHandler

@@ -1,3 +0,5 @@

const fs = require('fs')
const os = require('os')
const path = require('path')
const zlib = require('zlib')
const fse = require('fs-extra')
const miss = require('mississippi')

@@ -32,7 +34,13 @@ const split = require('split2')

const prefix = `${opts.dataset}-export-${slugDate}`
const assetHandler = new AssetHandler({client: options.client, archive, prefix})
const tmpDir = path.join(os.tmpdir(), prefix)
const assetHandler = new AssetHandler({
client: options.client,
tmpDir,
prefix
})
debug('Outputting assets (temporarily) to %s', tmpDir)
debug('Outputting to %s', options.outputPath === '-' ? 'stdout' : options.outputPath)
const outputStream =
options.outputPath === '-' ? process.stdout : fs.createWriteStream(options.outputPath)
options.outputPath === '-' ? process.stdout : fse.createWriteStream(options.outputPath)

@@ -53,3 +61,2 @@ let assetStreamHandler = assetHandler.noop

debug('Archive finished!')
resolve()
})

@@ -59,2 +66,23 @@

onProgress({step: 'Exporting documents...'})
let documentCount = 0
let lastReported = Date.now()
const reportDocumentCount = (chunk, enc, cb) => {
++documentCount
const now = Date.now()
if (now - lastReported > 50) {
onProgress({
step: 'Exporting documents...',
current: documentCount,
total: '?',
update: true
})
lastReported = now
}
cb(null, chunk)
}
const inputStream = await getDocumentsStream(options.client, options.dataset)

@@ -69,3 +97,4 @@ const jsonStream = miss.pipeline(

options.drafts ? miss.through.obj() : filterDrafts,
stringifyStream
stringifyStream,
miss.through(reportDocumentCount)
)

@@ -78,14 +107,28 @@

onProgress({
step: 'Exporting documents...',
current: documentCount,
total: documentCount,
update: true
})
if (!options.raw && options.assets) {
onProgress({step: 'Downloading assets...'})
}
archive.on('progress', ({entries}) => {
onProgress({
step: 'Downloading assets...',
current: entries.processed,
total: Math.max(assetHandler.queueSize, entries.processed),
update: true
})
let prevCompleted = 0
const progressInterval = setInterval(() => {
const completed = assetHandler.queueSize - assetHandler.queue.size
if (prevCompleted === completed) {
return
}
prevCompleted = completed
onProgress({
step: 'Downloading assets...',
current: completed,
total: assetHandler.queueSize,
update: true
})
}
}, 500)

@@ -95,3 +138,5 @@ debug('Waiting for asset handler to complete downloads')

await assetHandler.finish()
clearInterval(progressInterval)
} catch (assetErr) {
clearInterval(progressInterval)
reject(assetErr)

@@ -101,11 +146,24 @@ return

// Add all downloaded assets to archive
archive.directory(path.join(tmpDir, 'files'), `${prefix}/files`, {store: true})
archive.directory(path.join(tmpDir, 'images'), `${prefix}/images`, {store: true})
debug('Finalizing archive, flushing streams')
onProgress({step: 'Adding assets to archive...'})
archive.finalize()
})
archive.on('warning', err => {
debug('Archive warning: %s', err.message)
})
archive.append(jsonStream, {name: 'data.ndjson', prefix})
miss.pipe(archive, outputStream, onComplete)
function onComplete(err) {
async function onComplete(err) {
onProgress({step: 'Clearing temporary files...'})
await fse.remove(tmpDir)
if (!err) {
resolve()
return

@@ -112,0 +170,0 @@ }

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc