Skip to content

Commit

Permalink
feat: cli dump command
Browse files Browse the repository at this point in the history
  • Loading branch information
favoyang committed Jul 5, 2020
1 parent e12220e commit 7e7d9a2
Show file tree
Hide file tree
Showing 3 changed files with 90 additions and 15 deletions.
14 changes: 7 additions & 7 deletions README.md
Expand Up @@ -30,13 +30,13 @@ Redis is an in-memory database which is not good at dealing with large tarball f

## Data structure

| Content | Redis type | Redis key | Hash field |
|--------------|------------|---------------------|--------------|
| package list | set | ve:pkgs | - |
| secret | string | ve:secret | - |
| token | hash | ve:token:$user | $tokenKey |
| package.json | hash | ve:pkg:$packageName | package.json |
| tarball | hash | ve:pkg:$packageName | $fileName |
| Content type | Redis type | Redis key | Hash field | Note |
|--------------|------------|---------------------|--------------|----------------|
| package list | set | ve:pkgs | - | |
| secret | string | ve:secret | - | |
| token | hash | ve:token:$user | $tokenKey | |
| package.json | hash | ve:pkg:$packageName | package.json | |
| tarball | hash | ve:pkg:$packageName | $fileName | base64 encoded |

Data are stored with prefix `ve:` (`testve:` for test mode). You can optionally add global prefix via `config.prefix`.

Expand Down
89 changes: 82 additions & 7 deletions src/commands.ts
Expand Up @@ -5,10 +5,15 @@ import log from 'loglevel';
import { Command } from 'commander';
import { ClientOpts } from 'redis';
import { Logger } from '@verdaccio/types';
import mkdirp from 'mkdirp';
import { IHandyRedis } from 'handy-redis';

import { parseConfigFile, redisCreateClient } from './utils';
import PackageStorage, { PKG_FILE_NAME } from './PackageStorage';
import { parseConfigFile, redisCreateClient, REDIS_KEY } from './utils';
import Database from './db';

const VERDACCIO_DB_FILE = '.verdaccio-db.json';

log.setDefaultLevel('info');

/**
Expand Down Expand Up @@ -47,6 +52,9 @@ function parseRedisConfig(cmd: Command): ClientOpts {
}
}

/**
* Return a muted logger
*/
function getMutedLogger(): Logger {
// eslint-disable-next-line @typescript-eslint/no-empty-function
const mute = (): void => {};
Expand All @@ -62,8 +70,70 @@ function getMutedLogger(): Logger {
return wrapper;
}

interface CommandContext {
db: Database;
dir: string;
logger: Logger;
redisConfig: ClientOpts;
redisClient: IHandyRedis;
}

/**
* Dump database to the given directory
* @param db
* @param dir
*/
async function dumpDB({ db, dir }: CommandContext): Promise<void> {
log.info('Dump .verdaccio-db.json...');
const secret = await db.getSecret();
const packages = await db.get();
const data = { list: packages, secret };
const json = JSON.stringify(data);
const filePath = path.join(dir, VERDACCIO_DB_FILE);
log.info(` write ${filePath}`);
fs.writeFileSync(filePath, json);
}

/**
* Dump Redis storage to given directory
* Dump packument and tarball to the given directory
* @param db
* @param dir
*/
async function dumpPackages({ db, dir, redisClient }: CommandContext): Promise<void> {
log.info('Dump packages...');
const packages = await db.get();
for (const pkgName of packages) {
// Create folder
const pkgDir = path.join(dir, pkgName);
mkdirp.sync(pkgDir);
// Dump files
const pkgContents = await redisClient.hgetall(REDIS_KEY.package + pkgName);
for (const fileName of Object.keys(pkgContents)) {
if (fileName != 'stat') {
const filePath = path.join(pkgDir, fileName);
let fileContent = pkgContents[fileName];
log.info(` write ${filePath}`);
try {
if (fileName == PKG_FILE_NAME) {
// format package.json with 2 spaces indent
const json = JSON.parse(fileContent);
fileContent = JSON.stringify(json, null, 2);
await fs.writeFileSync(filePath, fileContent);
} else {
// write tarball
const buf = Buffer.from(fileContent, 'base64');
await fs.writeFileSync(filePath, buf);
}
} catch (err) {
log.error(`failed to write ${filePath}, err: ${err}`);
}
}
}
}
}

/**
* Dump Redis storage to the given directory
* @param dir
* @param cmd
*/
Expand All @@ -73,6 +143,13 @@ export async function dump(dir: string, cmd: Command): Promise<void> {
const redisConfig = parseRedisConfig(cmd);
const redisClient = redisCreateClient(redisConfig, mutedLogger);
const db = new Database(redisClient, mutedLogger);
const commandContext = {
db,
dir,
logger: mutedLogger,
redisConfig,
redisClient,
};
// parse dir
const absDir = path.resolve(dir || '.');
const stats = fs.lstatSync(absDir);
Expand All @@ -82,9 +159,7 @@ export async function dump(dir: string, cmd: Command): Promise<void> {
if (!stats.isDirectory()) {
throw new Error(`${dir} is not a directory`);
}
// dump packages
const pkgs = await db.get();
console.log(pkgs);
// dump package.json and tarballs
return Promise.resolve();
// dump
await dumpDB(commandContext);
await dumpPackages(commandContext);
}
2 changes: 1 addition & 1 deletion src/verdaccio-redis-cli.ts
Expand Up @@ -32,7 +32,7 @@ program

program
.command('dump [dir]')
.description('dump redis storage to dir')
.description('dump Redis storage to dir')
.action(async function(dir: string, cmd: Command) {
await runCommand(async () => {
await dump(dir, cmd);
Expand Down

0 comments on commit 7e7d9a2

Please sign in to comment.