@strapi/data-transfer
Version:
Data transfer capabilities for Strapi
260 lines (257 loc) • 12 kB
JavaScript
import path from 'path';
import { PassThrough } from 'stream';
import fs__default from 'fs-extra';
import { isEmpty, keyBy } from 'lodash/fp';
import { chain } from 'stream-chain';
import { parser } from 'stream-json/jsonl/Parser';
import 'crypto';
import { write } from '../../../utils/writable-async-write.mjs';
import { collect } from '../../../utils/stream.mjs';
import { schemasToValidJSON } from '../../../utils/schema.mjs';
import 'events';
import { ProviderInitializationError, ProviderTransferError } from '../../../errors/providers.mjs';
import { unknownPathToPosix } from '../../../file/providers/source/utils.mjs';
function _class_private_field_loose_base(receiver, privateKey) {
if (!Object.prototype.hasOwnProperty.call(receiver, privateKey)) {
throw new TypeError("attempted to use private field on non-instance");
}
return receiver;
}
var id = 0;
function _class_private_field_loose_key(name) {
return "__private_" + id++ + "_" + name;
}
const METADATA_FILE_PATH = 'metadata.json';
const createLocalDirectorySourceProvider = (options)=>{
return new LocalDirectorySourceProvider(options);
};
const isPathInsideRoot = (root, candidate)=>{
const relative = path.relative(root, candidate);
return relative === '' || !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative);
};
var _rootResolved = /*#__PURE__*/ _class_private_field_loose_key("_rootResolved"), _metadata = /*#__PURE__*/ _class_private_field_loose_key("_metadata"), _diagnostics = /*#__PURE__*/ _class_private_field_loose_key("_diagnostics"), _reportInfo = /*#__PURE__*/ _class_private_field_loose_key("_reportInfo"), /** Resolve a posix-style relative path under the export root; rejects escapes. */ _safePath = /*#__PURE__*/ _class_private_field_loose_key("_safePath"), _loadMetadata = /*#__PURE__*/ _class_private_field_loose_key("_loadMetadata"), _pipeAssetsToStream = /*#__PURE__*/ _class_private_field_loose_key("_pipeAssetsToStream"), _readAssetMetadata = /*#__PURE__*/ _class_private_field_loose_key("_readAssetMetadata"), _listJsonlFiles = /*#__PURE__*/ _class_private_field_loose_key("_listJsonlFiles"), _streamJsonlDirectory = /*#__PURE__*/ _class_private_field_loose_key("_streamJsonlDirectory"), _pipeJsonlDirectoryToStream = /*#__PURE__*/ _class_private_field_loose_key("_pipeJsonlDirectoryToStream");
class LocalDirectorySourceProvider {
async bootstrap(diagnostics) {
_class_private_field_loose_base(this, _diagnostics)[_diagnostics] = diagnostics;
const root = _class_private_field_loose_base(this, _rootResolved)[_rootResolved];
try {
const stat = await fs__default.stat(root);
if (!stat.isDirectory()) {
throw new ProviderInitializationError(`Path '${root}' is not a directory.`);
}
await _class_private_field_loose_base(this, _loadMetadata)[_loadMetadata]();
} catch (e) {
if (e instanceof ProviderInitializationError) {
throw e;
}
throw new ProviderInitializationError(`Directory '${root}' is not a valid Strapi data export.`);
}
if (!_class_private_field_loose_base(this, _metadata)[_metadata]) {
throw new ProviderInitializationError('Could not load metadata from Strapi data export.');
}
}
async getMetadata() {
_class_private_field_loose_base(this, _reportInfo)[_reportInfo]('getting metadata');
if (!_class_private_field_loose_base(this, _metadata)[_metadata]) {
await _class_private_field_loose_base(this, _loadMetadata)[_loadMetadata]();
}
return _class_private_field_loose_base(this, _metadata)[_metadata] ?? null;
}
async getSchemas() {
_class_private_field_loose_base(this, _reportInfo)[_reportInfo]('getting schemas');
const schemaCollection = await collect(this.createSchemasReadStream());
if (isEmpty(schemaCollection)) {
throw new ProviderInitializationError('Could not load schemas from Strapi data export.');
}
const schemas = keyBy('uid', schemaCollection);
return schemasToValidJSON(schemas);
}
createEntitiesReadStream() {
_class_private_field_loose_base(this, _reportInfo)[_reportInfo]('creating entities read stream');
return _class_private_field_loose_base(this, _streamJsonlDirectory)[_streamJsonlDirectory]('entities');
}
createSchemasReadStream() {
_class_private_field_loose_base(this, _reportInfo)[_reportInfo]('creating schemas read stream');
return _class_private_field_loose_base(this, _streamJsonlDirectory)[_streamJsonlDirectory]('schemas');
}
createLinksReadStream() {
_class_private_field_loose_base(this, _reportInfo)[_reportInfo]('creating links read stream');
return _class_private_field_loose_base(this, _streamJsonlDirectory)[_streamJsonlDirectory]('links');
}
createConfigurationReadStream() {
_class_private_field_loose_base(this, _reportInfo)[_reportInfo]('creating configuration read stream');
return _class_private_field_loose_base(this, _streamJsonlDirectory)[_streamJsonlDirectory]('configuration');
}
createAssetsReadStream() {
const outStream = new PassThrough({
objectMode: true
});
const uploadsDir = _class_private_field_loose_base(this, _safePath)[_safePath]('assets', 'uploads');
_class_private_field_loose_base(this, _reportInfo)[_reportInfo]('creating assets read stream');
_class_private_field_loose_base(this, _pipeAssetsToStream)[_pipeAssetsToStream](outStream, uploadsDir).catch((e)=>{
outStream.destroy(e instanceof Error ? e : new ProviderTransferError(String(e), {
details: {
error: e
}
}));
});
return outStream;
}
constructor(options){
Object.defineProperty(this, _reportInfo, {
value: reportInfo
});
Object.defineProperty(this, _safePath, {
value: safePath
});
Object.defineProperty(this, _loadMetadata, {
value: loadMetadata
});
Object.defineProperty(this, _pipeAssetsToStream, {
value: pipeAssetsToStream
});
Object.defineProperty(this, _readAssetMetadata, {
value: readAssetMetadata
});
Object.defineProperty(this, _listJsonlFiles, {
value: listJsonlFiles
});
Object.defineProperty(this, _streamJsonlDirectory, {
value: streamJsonlDirectory
});
Object.defineProperty(this, _pipeJsonlDirectoryToStream, {
value: pipeJsonlDirectoryToStream
});
Object.defineProperty(this, _rootResolved, {
writable: true,
value: void 0
});
Object.defineProperty(this, _metadata, {
writable: true,
value: void 0
});
Object.defineProperty(this, _diagnostics, {
writable: true,
value: void 0
});
this.type = 'source';
this.name = 'source::local-directory';
this.options = options;
_class_private_field_loose_base(this, _rootResolved)[_rootResolved] = path.resolve(options.directory.path);
}
}
function reportInfo(message) {
_class_private_field_loose_base(this, _diagnostics)[_diagnostics]?.report({
details: {
createdAt: new Date(),
message,
origin: 'directory-source-provider'
},
kind: 'info'
});
}
function safePath(...posixSegments) {
const joined = path.posix.join(...posixSegments);
const segments = joined.split(path.posix.sep).filter(Boolean);
const resolved = path.resolve(_class_private_field_loose_base(this, _rootResolved)[_rootResolved], ...segments);
if (!isPathInsideRoot(_class_private_field_loose_base(this, _rootResolved)[_rootResolved], resolved)) {
throw new ProviderInitializationError(`Invalid path "${joined}" — escapes backup directory`);
}
return resolved;
}
async function loadMetadata() {
const metadataPath = _class_private_field_loose_base(this, _safePath)[_safePath](METADATA_FILE_PATH);
if (!await fs__default.pathExists(metadataPath)) {
throw new ProviderInitializationError(`Missing ${METADATA_FILE_PATH} in export directory '${_class_private_field_loose_base(this, _rootResolved)[_rootResolved]}'.`);
}
_class_private_field_loose_base(this, _metadata)[_metadata] = await fs__default.readJson(metadataPath);
}
async function pipeAssetsToStream(outStream, uploadsDir) {
if (!await fs__default.pathExists(uploadsDir)) {
outStream.end();
return;
}
const names = (await fs__default.readdir(uploadsDir)).sort();
for (const name of names){
const absUpload = path.join(uploadsDir, name);
const stat = await fs__default.stat(absUpload);
if (stat.isFile()) {
let metadata;
try {
metadata = await _class_private_field_loose_base(this, _readAssetMetadata)[_readAssetMetadata](name);
} catch (error) {
outStream.destroy(new ProviderTransferError(`Failed to read metadata for ${name}`, {
details: {
error
}
}));
return;
}
const normalizedPath = unknownPathToPosix(path.posix.join('assets', 'uploads', name));
const asset = {
metadata,
filename: name,
filepath: normalizedPath,
stats: {
size: stat.size
},
stream: fs__default.createReadStream(absUpload)
};
await write(outStream, asset);
}
}
outStream.end();
}
async function readAssetMetadata(filename) {
const metadataPath = _class_private_field_loose_base(this, _safePath)[_safePath]('assets', 'metadata', `${filename}.json`);
return fs__default.readJson(metadataPath);
}
async function listJsonlFiles(posixSubdir) {
const dirAbs = _class_private_field_loose_base(this, _safePath)[_safePath](...posixSubdir.split('/').filter(Boolean));
if (!await fs__default.pathExists(dirAbs)) {
return [];
}
const names = await fs__default.readdir(dirAbs);
return names.filter((n)=>n.endsWith('.jsonl')).sort().map((n)=>path.join(dirAbs, n));
}
function streamJsonlDirectory(posixSubdir) {
const outStream = new PassThrough({
objectMode: true
});
_class_private_field_loose_base(this, _reportInfo)[_reportInfo](`streaming jsonl from ${posixSubdir}`);
_class_private_field_loose_base(this, _pipeJsonlDirectoryToStream)[_pipeJsonlDirectoryToStream](outStream, posixSubdir).catch((e)=>{
outStream.destroy(e instanceof Error ? e : new ProviderTransferError(String(e), {
details: {
error: e
}
}));
});
return outStream;
}
async function pipeJsonlDirectoryToStream(outStream, posixSubdir) {
const files = await _class_private_field_loose_base(this, _listJsonlFiles)[_listJsonlFiles](posixSubdir);
for (const absPath of files){
const transforms = [
parser({
checkErrors: true
}),
(line)=>line.value
];
const stream = fs__default.createReadStream(absPath).pipe(chain(transforms));
try {
for await (const chunk of stream){
await write(outStream, chunk);
}
} catch (e) {
outStream.destroy(new ProviderTransferError(`Error parsing JSONL in ${absPath}: ${e.message}`, {
details: {
error: e
}
}));
return;
}
}
outStream.end();
}
export { createLocalDirectorySourceProvider };
//# sourceMappingURL=index.mjs.map