@strapi/strapi
Version:
An open source headless CMS solution to create and manage your own API. It provides a powerful dashboard and features to make your life easier. Databases supported: MySQL, MariaDB, PostgreSQL, SQLite
241 lines (237 loc) • 9.9 kB
JavaScript
;
var fp = require('lodash/fp');
var ora = require('ora');
var dataTransfer = require('@strapi/data-transfer');
var dataTransfer$1 = require('../../utils/data-transfer.js');
var helpers = require('../../utils/helpers.js');
const { createTransferEngine } = dataTransfer.engine;
const { providers: { createRemoteStrapiDestinationProvider, createLocalStrapiSourceProvider, createLocalStrapiDestinationProvider, createRemoteStrapiSourceProvider } } = dataTransfer.strapi;
const resolveRemotePullAssetIdleTimeoutMs = (value)=>{
if (value == null || value === '') {
return undefined;
}
const n = typeof value === 'number' ? value : Number(value);
if (!Number.isFinite(n) || n <= 0) {
return undefined;
}
return n;
};
/**
* Transfer command.
*
* Transfers data between local Strapi and remote Strapi instances
*/ var action = (async (opts)=>{
// Avoid DeprecationWarning lines on stderr (e.g. pg `client.query()` while a query is in flight)
// interleaving with ora spinners during transfer. (Runtime API; not on all @types/node Process typings.)
process.noDeprecation = true;
// Validate inputs from Commander
if (!fp.isObject(opts)) {
helpers.exitWith(1, 'Could not parse command arguments');
}
if (!(opts.from || opts.to) || opts.from && opts.to) {
helpers.exitWith(1, 'Exactly one source (from) or destination (to) option must be provided');
}
const strapi = await dataTransfer$1.createStrapiInstance();
const checksumsEnabled = opts.checksums !== false;
let source;
let destination;
// if no URL provided, use local Strapi
if (!opts.from) {
source = createLocalStrapiSourceProvider({
getStrapi: ()=>strapi
});
} else {
if (!opts.fromToken) {
helpers.exitWith(1, 'Missing token for remote destination');
}
const assetIdleTimeoutMs = resolveRemotePullAssetIdleTimeoutMs(strapi.config.get('server.transfer.remote.assetIdleTimeoutMs'));
source = createRemoteStrapiSourceProvider({
getStrapi: ()=>strapi,
url: opts.from,
auth: {
type: 'token',
token: opts.fromToken
},
...assetIdleTimeoutMs !== undefined ? {
streamTimeout: assetIdleTimeoutMs
} : {},
...checksumsEnabled ? {
verifyChecksums: true
} : {}
});
}
/** Wired after `engine` exists so destination prep can update the CLI spinner. */ const transferPhaseBridge = {
emit () {
/* replaced below once `progress` exists */ }
};
// if no URL provided, use local Strapi
if (!opts.to) {
destination = createLocalStrapiDestinationProvider({
getStrapi: ()=>strapi,
strategy: 'restore',
restore: dataTransfer$1.parseRestoreFromOptions(opts, strapi),
onTransferPhase: (message)=>transferPhaseBridge.emit(message)
});
} else {
if (!opts.toToken) {
helpers.exitWith(1, 'Missing token for remote destination');
}
destination = createRemoteStrapiDestinationProvider({
url: opts.to,
auth: {
type: 'token',
token: opts.toToken
},
strategy: 'restore',
restore: dataTransfer$1.parseRestoreFromOptions(opts, strapi),
onTransferPhase: (message)=>transferPhaseBridge.emit(message),
...checksumsEnabled ? {
verifyChecksums: true
} : {}
});
}
if (!source || !destination) {
helpers.exitWith(1, 'Could not create providers');
}
const engine = createTransferEngine(source, destination, {
versionStrategy: 'exact',
schemaStrategy: 'strict',
exclude: opts.exclude,
only: opts.only,
throttle: opts.throttle,
transforms: {
links: [
{
filter (link) {
return !dataTransfer$1.isIgnoredContentType(link.left.type) && !dataTransfer$1.isIgnoredContentType(link.right.type);
}
}
],
entities: [
{
filter (entity) {
return !dataTransfer$1.isIgnoredContentType(entity.type);
}
}
]
}
});
engine.diagnostics.onDiagnostic(dataTransfer$1.formatDiagnostic('transfer', opts.verbose));
const progress = engine.progress.stream;
/** Shown until destination prep emits a step; then we keep this prefix and append the step after " — ". */ const STARTING_TRANSFER_PREFIX = 'Starting transfer…';
let prepStepDetail = null;
const formatPrepSpinnerLine = ()=>prepStepDetail != null && prepStepDetail !== '' ? `${STARTING_TRANSFER_PREFIX} — ${prepStepDetail}` : STARTING_TRANSFER_PREFIX;
transferPhaseBridge.emit = (message)=>{
prepStepDetail = message;
progress.emit('transfer::phase', {
message: formatPrepSpinnerLine()
});
};
const { updateLoader } = dataTransfer$1.loadersFactory();
let startingSpinner = null;
let startingElapsedInterval = null;
/** Set when `transfer::start` fires so we can print a final persisted line with elapsed time. */ let transferPrepStartedAt = null;
/**
* Stops the "starting transfer" spinner and **leaves a finished line** in the console (like stage
* `succeed`/`fail`), so the next stage spinner starts on a new line instead of replacing this one.
*/ const finishStartingSpinner = (outcome = 'done')=>{
if (startingElapsedInterval) {
clearInterval(startingElapsedInterval);
startingElapsedInterval = null;
}
if (startingSpinner) {
const elapsed = transferPrepStartedAt != null ? Date.now() - transferPrepStartedAt : 0;
const line = `${formatPrepSpinnerLine()}${helpers.TRANSFER_PROGRESS_FIELD_SEP}${helpers.formatElapsedAndMaybeRemainingLabel(elapsed, null)}`;
if (outcome === 'fail') {
startingSpinner.fail(line);
} else {
startingSpinner.succeed(line);
}
startingSpinner = null;
transferPrepStartedAt = null;
}
};
engine.onSchemaDiff(dataTransfer$1.getDiffHandler(engine, {
force: opts.force,
action: 'transfer'
}));
engine.addErrorHandler('ASSETS_DIRECTORY_ERR', dataTransfer$1.getAssetsBackupHandler(engine, {
force: opts.force,
action: 'transfer'
}));
// Update more frequently to ensure elapsed time is accurate even if the stage is not progressing
const activeStages = new Set();
const lastStageData = {};
const interval = setInterval(()=>{
for (const stage of activeStages){
if (lastStageData[stage]) {
// Clone the lastStageData and ensure endTime is undefined so elapsed uses Date.now()
const dataCopy = {
...lastStageData[stage],
endTime: undefined
};
updateLoader(stage, {
[stage]: dataCopy
});
}
}
}, 100);
progress.on(`stage::start`, ({ stage, data })=>{
finishStartingSpinner('done');
updateLoader(stage, data).start();
});
progress.on('stage::finish', ({ stage, data })=>{
updateLoader(stage, data).succeed();
});
progress.on('stage::progress', ({ stage, data })=>{
lastStageData[stage] = data[stage];
activeStages.add(stage);
updateLoader(stage, data);
});
progress.on('stage::error', ({ stage, data })=>{
updateLoader(stage, data).fail();
});
progress.on('transfer::finish', ()=>{
finishStartingSpinner('done');
clearInterval(interval);
});
progress.on('transfer::error', ()=>{
finishStartingSpinner('fail');
clearInterval(interval);
});
progress.on('transfer::start', async ()=>{
transferPrepStartedAt = Date.now();
prepStepDetail = null;
startingSpinner = ora(formatPrepSpinnerLine()).start();
startingElapsedInterval = setInterval(()=>{
if (startingSpinner && transferPrepStartedAt != null) {
const elapsed = Date.now() - transferPrepStartedAt;
startingSpinner.text = `${formatPrepSpinnerLine()}${helpers.TRANSFER_PROGRESS_FIELD_SEP}${helpers.formatElapsedAndMaybeRemainingLabel(elapsed, null)}`;
}
}, 100);
await strapi.telemetry.send('didDEITSProcessStart', dataTransfer$1.getTransferTelemetryPayload(engine));
});
let results;
try {
// Abort transfer if user interrupts process
dataTransfer$1.setSignalHandler(()=>dataTransfer$1.abortTransfer({
engine,
strapi
}));
results = await engine.transfer();
// Note: we need to await telemetry or else the process ends before it is sent
await strapi.telemetry.send('didDEITSProcessFinish', dataTransfer$1.getTransferTelemetryPayload(engine));
try {
const table = dataTransfer$1.buildTransferTable(results.engine);
console.log(table?.toString());
} catch (e) {
console.error('There was an error displaying the results of the transfer.');
}
helpers.exitWith(0, dataTransfer$1.exitMessageText('transfer'));
} catch (e) {
await strapi.telemetry.send('didDEITSProcessFail', dataTransfer$1.getTransferTelemetryPayload(engine));
helpers.exitWith(1, dataTransfer$1.exitMessageText('transfer', true));
}
});
module.exports = action;
//# sourceMappingURL=action.js.map