Merge branch 'features/data-transfer' of github.com:strapi/strapi into features/data-transfer

This commit is contained in:
Convly 2023-02-03 12:11:02 +01:00
commit cafceb9e5e
3 changed files with 89 additions and 62 deletions

View File

@ -18,21 +18,15 @@ import type { ILocalStrapiDestinationProviderOptions } from '../local-destinatio
import { TRANSFER_PATH } from '../../remote/constants';
import { ProviderTransferError, ProviderValidationError } from '../../../errors/providers';
interface ITokenAuth {
interface ITransferTokenAuth {
type: 'token';
token: string;
}
interface ICredentialsAuth {
type: 'credentials';
email: string;
password: string;
}
export interface IRemoteStrapiDestinationProviderOptions
extends Pick<ILocalStrapiDestinationProviderOptions, 'restore' | 'strategy'> {
url: URL;
auth?: ITokenAuth | ICredentialsAuth;
auth?: ITransferTokenAuth;
}
class RemoteStrapiDestinationProvider implements IDestinationProvider {

View File

@ -273,59 +273,87 @@ program
.option('-s, --silent', `Run the generation silently, without any output`, false)
.action(getLocalScript('ts/generate-types'));
if (process.env.STRAPI_EXPERIMENTAL === 'true') {
// `$ strapi transfer`
program
.command('transfer')
.description('Transfer data from one source to another')
.allowExcessArguments(false)
.addOption(
new Option(
'--from <sourceURL>',
`URL of the remote Strapi instance to get data from`
).argParser(parseURL)
// `$ strapi transfer`
program
.command('transfer')
.description('Transfer data from one source to another')
.allowExcessArguments(false)
.addOption(
new Option('--from <sourceURL>', `URL of the remote Strapi instance to get data from`)
.argParser(parseURL)
.hideHelp() // Hidden until pull feature is released
)
.addOption(
new Option('--from-token <token>', `Transfer token for the remote Strapi source`).hideHelp() // Hidden until pull feature is released
)
.addOption(
new Option('--to <destinationURL>', `URL of the remote Strapi instance to send data to`)
.argParser(parseURL)
.required()
)
.addOption(new Option('--to-token <token>', `Transfer token for the remote Strapi destination`))
.addOption(forceOption)
.addOption(excludeOption)
.addOption(onlyOption)
.hook('preAction', validateExcludeOnly)
// If --from is used, validate the URL and token
.hook(
'preAction',
ifOptions(
(opts) => opts.from,
async (thisCommand) => {
assertUrlHasProtocol(thisCommand.opts().from, ['https:', 'http:']);
if (!thisCommand.opts().fromToken) {
const answers = await inquirer.prompt([
{
type: 'password',
message: 'Please enter your transfer token for the remote Strapi source',
name: 'fromToken',
},
]);
if (!answers.fromToken?.length) {
exitWith(0, 'No token entered, aborting transfer.');
}
thisCommand.opts().fromToken = answers.fromToken;
}
}
)
.addOption(
new Option(
'--to <destinationURL>',
`URL of the remote Strapi instance to send data to`
).argParser(parseURL)
)
// If --to is used, validate the URL, token, and confirm restore
.hook(
'preAction',
ifOptions(
(opts) => opts.to,
async (thisCommand) => {
assertUrlHasProtocol(thisCommand.opts().to, ['https:', 'http:']);
if (!thisCommand.opts().toToken) {
const answers = await inquirer.prompt([
{
type: 'password',
message: 'Please enter your transfer token for the remote Strapi destination',
name: 'toToken',
},
]);
if (!answers.toToken?.length) {
exitWith(0, 'No token entered, aborting transfer.');
}
thisCommand.opts().toToken = answers.toToken;
}
await confirmMessage(
'The transfer will delete all data in the remote database and media files. Are you sure you want to proceed?'
)(thisCommand);
}
)
.addOption(forceOption)
// Validate URLs
.hook(
'preAction',
ifOptions(
(opts) => opts.from,
(thisCommand) => assertUrlHasProtocol(thisCommand.opts().from, ['https:', 'http:'])
)
)
.hook(
'preAction',
ifOptions(
(opts) => opts.to,
(thisCommand) => assertUrlHasProtocol(thisCommand.opts().to, ['https:', 'http:'])
)
)
.hook(
'preAction',
ifOptions(
(opts) => !opts.from && !opts.to,
() => exitWith(1, 'At least one source (from) or destination (to) option must be provided')
)
)
.addOption(forceOption)
.addOption(excludeOption)
.addOption(onlyOption)
.hook('preAction', validateExcludeOnly)
.hook(
'preAction',
confirmMessage(
'The import will delete all data in the remote database. Are you sure you want to proceed?'
)
)
.action(getLocalScript('transfer/transfer'));
}
)
// .hook(
// 'preAction',
// ifOptions(
// (opts) => !opts.from && !opts.to,
// () => exitWith(1, 'At least one source (from) or destination (to) option must be provided')
// )
// )
.action(getLocalScript('transfer/transfer'));
// `$ strapi export`
program
@ -421,7 +449,7 @@ program
.hook(
'preAction',
confirmMessage(
'The import will delete all data in your database. Are you sure you want to proceed?'
'The import will delete all data in your database and media files. Are you sure you want to proceed?'
)
)
.action(getLocalScript('transfer/import'));

View File

@ -25,6 +25,8 @@ const logger = console;
*
* @property {URL|undefined} [to] The url of a remote Strapi to use as remote destination
* @property {URL|undefined} [from] The url of a remote Strapi to use as remote source
* @property {string|undefined} [toToken] The transfer token for the remote Strapi destination
* @property {string|undefined} [fromToken] The transfer token for the remote Strapi source
*/
/**
@ -73,7 +75,10 @@ module.exports = async (opts) => {
else {
destination = createRemoteStrapiDestinationProvider({
url: opts.to,
auth: false,
auth: {
type: 'token',
token: opts.toToken,
},
strategy: 'restore',
restore: {
entities: { exclude: DEFAULT_IGNORED_CONTENT_TYPES },
@ -87,7 +92,7 @@ module.exports = async (opts) => {
}
const engine = createTransferEngine(source, destination, {
versionStrategy: 'strict',
versionStrategy: 'exact',
schemaStrategy: 'strict',
transforms: {
links: [