UNPKG

@strapi/data-transfer

Version:

Data transfer capabilities for Strapi

260 lines (257 loc) 12 kB
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