Add remote sync command and tests
This commit is contained in:
@@ -50,6 +50,8 @@ this will check the health, and if not setup, it will prompt you to set up the C
|
|||||||
- `shsf file overwrite`: Overwrite an existing file in a storage. (use `shsf file overwrite -h` first)
|
- `shsf file overwrite`: Overwrite an existing file in a storage. (use `shsf file overwrite -h` first)
|
||||||
- `shsf file delete`: Delete a file from a storage. (use `shsf file delete -h` first)
|
- `shsf file delete`: Delete a file from a storage. (use `shsf file delete -h` first)
|
||||||
- `shsf file list`: List files in a storage. (use `shsf file list -h` first)
|
- `shsf file list`: List files in a storage. (use `shsf file list -h` first)
|
||||||
|
- `shsf remote --id <id> --into <path>`: Pull files from a function into a local directory.
|
||||||
|
- `shsf remote --id <id> --from <path> [--force]`: Push files from a local directory to a function.
|
||||||
|
|
||||||
## Instructions
|
## Instructions
|
||||||
Use these commands for when you need to interact with shsf from the command line. Its faster than using the ui for almost all ops.
|
Use these commands for when you need to interact with shsf from the command line. Its faster than using the ui for almost all ops.
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "shsf-cli",
|
"name": "shsf-cli",
|
||||||
"version": "2.1.6",
|
"version": "2.1.7",
|
||||||
"description": "",
|
"description": "",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"files": [
|
"files": [
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import { describe, it, expect, vi } from 'vitest';
|
||||||
|
import { action } from '../commands/remote.js';
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
vi.mock('axios');
|
||||||
|
|
||||||
|
describe('remote command', () => {
|
||||||
|
it('should validate inputs', async () => {
|
||||||
|
// Simple test for now
|
||||||
|
await expect(action({})).resolves.not.toThrow();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
import { Command } from 'commander';
|
||||||
|
import axios from 'axios';
|
||||||
|
import { loadConfig } from '../config.js';
|
||||||
|
import chalk from 'chalk';
|
||||||
|
import fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
|
||||||
|
export async function action(options: any) {
|
||||||
|
const config = await loadConfig();
|
||||||
|
const api = axios.create({
|
||||||
|
baseURL: config.apiBaseUrl,
|
||||||
|
headers: { 'Authorization': `Bearer ${config.token}` }
|
||||||
|
});
|
||||||
|
|
||||||
|
if (options.into) {
|
||||||
|
// PULL
|
||||||
|
try {
|
||||||
|
const response = await api.get(`/functions/${options.id}/files`);
|
||||||
|
const files = response.data; // Assuming { filename: string, content: string }[]
|
||||||
|
|
||||||
|
if (!fs.existsSync(options.into)) {
|
||||||
|
fs.mkdirSync(options.into, { recursive: true });
|
||||||
|
} else if (fs.readdirSync(options.into).length > 0 && !options.force) {
|
||||||
|
console.error(chalk.red(`Directory ${options.into} is not empty. Use --force to overwrite.`));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const file of files) {
|
||||||
|
fs.writeFileSync(path.join(options.into, file.filename), file.content);
|
||||||
|
}
|
||||||
|
console.log(chalk.green(`Successfully pulled files into ${options.into}`));
|
||||||
|
} catch (e) {
|
||||||
|
console.error(chalk.red('Failed to pull files'), e);
|
||||||
|
}
|
||||||
|
} else if (options.from) {
|
||||||
|
// PUSH
|
||||||
|
if (!fs.existsSync(options.from)) {
|
||||||
|
console.error(chalk.red(`Source ${options.from} does not exist.`));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const files = fs.readdirSync(options.from).map(file => ({
|
||||||
|
filename: file,
|
||||||
|
content: fs.readFileSync(path.join(options.from, file), 'utf-8')
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (!options.force) {
|
||||||
|
console.log(chalk.yellow(`Pushing ${files.length} files. Review: ${files.map(f => f.filename).join(', ')}`));
|
||||||
|
|
||||||
|
if (files.length > 5) {
|
||||||
|
console.log(chalk.yellow('Too many modifications. Please use --force to confirm.'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await api.post(`/functions/${options.id}/files`, { files });
|
||||||
|
console.log(chalk.green('Successfully pushed files.'));
|
||||||
|
} catch (e) {
|
||||||
|
console.error(chalk.red('Failed to push files'), e);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.error(chalk.red('Must specify --into or --from'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'remote',
|
||||||
|
description: 'sync remote functions to/from local storage',
|
||||||
|
options: [
|
||||||
|
{ name: '--id <id>', description: 'function id', required: true },
|
||||||
|
{ name: '--into <path>', description: 'target directory for pull' },
|
||||||
|
{ name: '--from <path>', description: 'source directory for push' },
|
||||||
|
{ name: '--force', description: 'force push/pull without confirmation' }
|
||||||
|
],
|
||||||
|
action
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user