mirror of
https://github.com/strapi/strapi.git
synced 2025-09-25 16:29:34 +00:00
refactor
This commit is contained in:
parent
544fb057c2
commit
11b8d3c9f3
@ -2,6 +2,7 @@ import { getStrapiFactory } from '../../__tests__/test-utils';
|
||||
|
||||
import { createTransferHandler } from '../remote/handlers';
|
||||
import register from '../register';
|
||||
import { TRANSFER_PATH } from '../../../lib/strapi/remote/constants';
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
@ -20,13 +21,13 @@ jest.mock('../remote/handlers', () => ({
|
||||
}));
|
||||
|
||||
describe('Register the Transfer route', () => {
|
||||
test('registers the /transfer route', () => {
|
||||
test('registers the transfer route', () => {
|
||||
const strapi = strapiMockFactory();
|
||||
|
||||
register(strapi);
|
||||
expect(strapi.admin.routes.push).toHaveBeenCalledWith({
|
||||
method: 'GET',
|
||||
path: '/transfer',
|
||||
path: TRANSFER_PATH,
|
||||
handler: createTransferHandler(),
|
||||
config: {
|
||||
auth: false,
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { WebSocket } from 'ws';
|
||||
import { TRANSFER_PATH } from '../../../../../lib/strapi/remote/constants';
|
||||
import { CommandMessage } from '../../../../../types/remote/protocol/client';
|
||||
import { createDispatcher } from '../utils';
|
||||
|
||||
@ -18,7 +19,7 @@ afterEach(() => {
|
||||
|
||||
describe('Remote Strapi Destination Utils', () => {
|
||||
test('Dispatch method sends payload', () => {
|
||||
const ws = new WebSocket('ws://test/admin/transfer');
|
||||
const ws = new WebSocket(`ws://test/admin${TRANSFER_PATH}`);
|
||||
const message: CommandMessage = {
|
||||
type: 'command',
|
||||
command: 'status',
|
||||
|
@ -15,6 +15,7 @@ import type {
|
||||
} from '../../../../types';
|
||||
import type { client, server } from '../../../../types/remote/protocol';
|
||||
import type { ILocalStrapiDestinationProviderOptions } from '../local-destination';
|
||||
import { TRANSFER_PATH } from '../../remote/constants';
|
||||
|
||||
interface ITokenAuth {
|
||||
type: 'token';
|
||||
@ -29,7 +30,7 @@ interface ICredentialsAuth {
|
||||
|
||||
export interface IRemoteStrapiDestinationProviderOptions
|
||||
extends Pick<ILocalStrapiDestinationProviderOptions, 'restore' | 'strategy'> {
|
||||
url: string;
|
||||
url: URL;
|
||||
auth?: ITokenAuth | ICredentialsAuth;
|
||||
}
|
||||
|
||||
@ -100,16 +101,19 @@ class RemoteStrapiDestinationProvider implements IDestinationProvider {
|
||||
|
||||
let ws: WebSocket;
|
||||
|
||||
const wsUrl = `${url.protocol === 'https:' ? 'wss:' : 'ws:'}//${url.host}${
|
||||
url.pathname
|
||||
}${TRANSFER_PATH}`;
|
||||
|
||||
// No auth defined, trying public access for transfer
|
||||
if (!auth) {
|
||||
ws = new WebSocket(url);
|
||||
ws = new WebSocket(wsUrl);
|
||||
}
|
||||
|
||||
// Common token auth, this should be the main auth method
|
||||
else if (auth.type === 'token') {
|
||||
const headers = { Authentication: `Bearer ${auth.token}` };
|
||||
|
||||
ws = new WebSocket(this.options.url, { headers });
|
||||
const headers = { Authorization: `Bearer ${auth.token}` };
|
||||
ws = new WebSocket(wsUrl, { headers });
|
||||
}
|
||||
|
||||
// Invalid auth method provided
|
||||
|
@ -1 +1 @@
|
||||
export const TRANSFER_URL = '/transfer';
|
||||
export const TRANSFER_PATH = '/transfer';
|
||||
|
@ -1,7 +1,7 @@
|
||||
// eslint-disable-next-line node/no-extraneous-import
|
||||
import type { Context } from 'koa';
|
||||
|
||||
import { TRANSFER_URL } from './constants';
|
||||
import { TRANSFER_PATH } from './constants';
|
||||
import { createTransferHandler } from './handlers';
|
||||
|
||||
// Extend Strapi interface type to access the admin routes' API
|
||||
@ -29,7 +29,7 @@ declare module '@strapi/strapi' {
|
||||
export const registerAdminTransferRoute = (strapi: Strapi.Strapi) => {
|
||||
strapi.admin.routes.push({
|
||||
method: 'GET',
|
||||
path: TRANSFER_URL,
|
||||
path: TRANSFER_PATH,
|
||||
handler: createTransferHandler(),
|
||||
config: { auth: false },
|
||||
});
|
||||
|
@ -14,7 +14,12 @@ const inquirer = require('inquirer');
|
||||
const program = new Command();
|
||||
|
||||
const packageJSON = require('../package.json');
|
||||
const { promptEncryptionKey, confirmMessage } = require('../lib/commands/utils/commander');
|
||||
const {
|
||||
promptEncryptionKey,
|
||||
confirmMessage,
|
||||
parseURL,
|
||||
} = require('../lib/commands/utils/commander');
|
||||
const { ifOptions, assertUrlHasProtocol, exitWith } = require('../lib/commands/utils/helpers');
|
||||
|
||||
const checkCwdIsStrapiApp = (name) => {
|
||||
const logErrorAndExit = () => {
|
||||
@ -263,16 +268,39 @@ if (process.env.STRAPI_EXPERIMENTAL) {
|
||||
program
|
||||
.command('transfer')
|
||||
.description('Transfer data from one source to another')
|
||||
.addOption(new Option('--from <sourceURL>', `URL of remote Strapi instance to get data from.`))
|
||||
.addOption(new Option('--to <destinationURL>', `URL of remote Strapi instance to send data to`))
|
||||
.hook('preAction', async (thisCommand) => {
|
||||
const opts = thisCommand.opts();
|
||||
|
||||
if (!opts.from && !opts.to) {
|
||||
console.error('At least one source (from) or destination (to) option must be provided');
|
||||
process.exit(1);
|
||||
}
|
||||
})
|
||||
.addOption(
|
||||
new Option('--from <sourceURL>', `URL of remote Strapi instance to get data from.`).argParser(
|
||||
parseURL
|
||||
)
|
||||
)
|
||||
.addOption(
|
||||
new Option(
|
||||
'--to <destinationURL>',
|
||||
`URL of remote Strapi instance to send data to`
|
||||
).argParser(parseURL)
|
||||
)
|
||||
// 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')
|
||||
)
|
||||
)
|
||||
.allowExcessArguments(false)
|
||||
.action(getLocalScript('transfer/transfer'));
|
||||
}
|
||||
|
@ -38,7 +38,7 @@ jest.spyOn(console, 'log').mockImplementation(() => {});
|
||||
|
||||
jest.mock('../../transfer/utils');
|
||||
|
||||
const destinationUrl = 'ws://strapi.com';
|
||||
const destinationUrl = new URL('http://strapi.com/admin');
|
||||
|
||||
describe('Transfer', () => {
|
||||
beforeEach(() => {
|
||||
|
@ -22,8 +22,8 @@ const logger = console;
|
||||
/**
|
||||
* @typedef TransferCommandOptions Options given to the CLI transfer command
|
||||
*
|
||||
* @property {string|undefined} [to] The url of a remote Strapi to use as remote destination
|
||||
* @property {string|undefined} [from] The url of a remote Strapi to use as remote source
|
||||
* @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
|
||||
*/
|
||||
|
||||
/**
|
||||
@ -86,8 +86,8 @@ module.exports = async (opts) => {
|
||||
}
|
||||
|
||||
const engine = createTransferEngine(source, destination, {
|
||||
versionStrategy: 'ignore', // for an export to file, versionStrategy will always be skipped
|
||||
schemaStrategy: 'ignore', // for an export to file, schemaStrategy will always be skipped
|
||||
versionStrategy: 'exact', // for an export to file, versionStrategy will always be skipped
|
||||
schemaStrategy: 'exact', // for an export to file, schemaStrategy will always be skipped
|
||||
transforms: {
|
||||
links: [
|
||||
{
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
const chalk = require('chalk');
|
||||
const Table = require('cli-table3');
|
||||
const { readableBytes } = require('../utils');
|
||||
const { readableBytes } = require('../utils/helpers');
|
||||
const strapi = require('../../index');
|
||||
|
||||
const pad = (n) => {
|
||||
|
@ -1,14 +1,36 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* This file includes hooks to use for commander.hook and argParsers for commander.argParser
|
||||
*/
|
||||
|
||||
const inquirer = require('inquirer');
|
||||
const { InvalidOptionArgumentError } = require('commander');
|
||||
const { exitWith } = require('./helpers');
|
||||
|
||||
/**
|
||||
* argsParser: Parse a comma-delimited string as an array
|
||||
* argParser: Parse a comma-delimited string as an array
|
||||
*/
|
||||
const parseInputList = (value) => {
|
||||
return value.split(',');
|
||||
};
|
||||
|
||||
/**
|
||||
* argParser: Parse a string as a URL object
|
||||
*/
|
||||
const parseURL = (value) => {
|
||||
try {
|
||||
const url = new URL(value);
|
||||
if (!url.host) {
|
||||
throw new InvalidOptionArgumentError(`Could not parse url ${value}`);
|
||||
}
|
||||
|
||||
return url;
|
||||
} catch (e) {
|
||||
throw new InvalidOptionArgumentError(`Could not parse url ${value}`);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* hook: if encrypt==true and key not provided, prompt for it
|
||||
*/
|
||||
@ -16,8 +38,7 @@ const promptEncryptionKey = async (thisCommand) => {
|
||||
const opts = thisCommand.opts();
|
||||
|
||||
if (!opts.encrypt && opts.key) {
|
||||
console.error('Key may not be present unless encryption is used');
|
||||
process.exit(1);
|
||||
return exitWith(1, 'Key may not be present unless encryption is used');
|
||||
}
|
||||
|
||||
// if encrypt==true but we have no key, prompt for it
|
||||
@ -37,12 +58,10 @@ const promptEncryptionKey = async (thisCommand) => {
|
||||
]);
|
||||
opts.key = answers.key;
|
||||
} catch (e) {
|
||||
console.error('Failed to get encryption key');
|
||||
process.exit(1);
|
||||
return exitWith(1, 'Failed to get encryption key');
|
||||
}
|
||||
if (!opts.key) {
|
||||
console.error('Failed to get encryption key');
|
||||
process.exit(1);
|
||||
return exitWith(1, 'Failed to get encryption key');
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -61,13 +80,15 @@ const confirmMessage = (message) => {
|
||||
},
|
||||
]);
|
||||
if (!answers.confirm) {
|
||||
process.exit(0);
|
||||
exitWith(0);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
parseInputList,
|
||||
parseURL,
|
||||
promptEncryptionKey,
|
||||
confirmMessage,
|
||||
exitWith,
|
||||
};
|
||||
|
108
packages/core/strapi/lib/commands/utils/helpers.js
Normal file
108
packages/core/strapi/lib/commands/utils/helpers.js
Normal file
@ -0,0 +1,108 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Helper functions for the Strapi CLI
|
||||
*/
|
||||
|
||||
const chalk = require('chalk');
|
||||
const { isString, isArray } = require('lodash/fp');
|
||||
|
||||
const bytesPerKb = 1024;
|
||||
const sizes = ['B ', 'KB', 'MB', 'GB', 'TB', 'PB'];
|
||||
|
||||
/**
|
||||
* Convert bytes to a human readable formatted string, for example "1024" becomes "1KB"
|
||||
*
|
||||
* @param {number} bytes The bytes to be converted
|
||||
* @param {number} decimals How many decimals to include in the final number
|
||||
* @param {number} padStart Pad the string with space at the beginning so that it has at least this many characters
|
||||
*/
|
||||
const readableBytes = (bytes, decimals = 1, padStart = 0) => {
|
||||
if (!bytes) {
|
||||
return '0';
|
||||
}
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(bytesPerKb));
|
||||
const result = `${parseFloat((bytes / bytesPerKb ** i).toFixed(decimals))} ${sizes[i].padStart(
|
||||
2
|
||||
)}`;
|
||||
|
||||
return result.padStart(padStart);
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* Display message(s) to console and then call process.exit with code.
|
||||
* If code is zero, console.log and green text is used for messages, otherwise console.error and red text.
|
||||
*
|
||||
* @param {number} code Code to exit process with
|
||||
* @param {string | Array} message Message(s) to display before exiting
|
||||
*/
|
||||
const exitWith = (code, message = undefined) => {
|
||||
const logger = (message) => {
|
||||
if (code === 0) {
|
||||
console.log(chalk.green(message));
|
||||
} else {
|
||||
console.log(chalk.red(message));
|
||||
}
|
||||
};
|
||||
|
||||
if (isString(message)) {
|
||||
logger(message);
|
||||
} else if (isArray(message)) {
|
||||
message.forEach((msg) => logger(msg));
|
||||
}
|
||||
process.exit(code);
|
||||
};
|
||||
|
||||
/**
|
||||
* assert that a URL object has a protocol value
|
||||
*
|
||||
* @param {URL} url
|
||||
* @param {string[]|string|undefined} [protocol]
|
||||
*/
|
||||
const assertUrlHasProtocol = (url, protocol = undefined) => {
|
||||
if (!url.protocol) {
|
||||
exitWith(1, `${url.toString()} does not have a protocol`);
|
||||
}
|
||||
|
||||
// if just checking for the existence of a protocol, return
|
||||
if (!protocol) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isString(protocol)) {
|
||||
if (protocol !== url.protocol) {
|
||||
exitWith(1, `${url.toString()} must have the protocol ${protocol}`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// assume an array
|
||||
if (!protocol.some((protocol) => url.protocol === protocol)) {
|
||||
return exitWith(
|
||||
1,
|
||||
`${url.toString()} must have one of the following protocols: ${protocol.join(',')}`
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Passes commander options to conditionCallback(). If it returns true, call isMetCallback otherwise call isNotMetCallback
|
||||
*/
|
||||
const ifOptions = (conditionCallback, isMetCallback = () => {}, isNotMetCallback = () => {}) => {
|
||||
return async (command) => {
|
||||
const opts = command.opts();
|
||||
if (await conditionCallback(opts)) {
|
||||
await isMetCallback(command);
|
||||
} else {
|
||||
await isNotMetCallback(command);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
exitWith,
|
||||
assertUrlHasProtocol,
|
||||
ifOptions,
|
||||
readableBytes,
|
||||
};
|
@ -1,20 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const bytesPerKb = 1024;
|
||||
const sizes = ['B ', 'KB', 'MB', 'GB', 'TB', 'PB'];
|
||||
|
||||
const readableBytes = (bytes, decimals = 1, padStart = 0) => {
|
||||
if (!bytes) {
|
||||
return '0';
|
||||
}
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(bytesPerKb));
|
||||
const result = `${parseFloat((bytes / bytesPerKb ** i).toFixed(decimals))} ${sizes[i].padStart(
|
||||
2
|
||||
)}`;
|
||||
|
||||
return result.padStart(padStart);
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
readableBytes,
|
||||
};
|
Loading…
x
Reference in New Issue
Block a user