Add remote sync command and tests

This commit is contained in:
2026-03-20 16:21:53 +01:00
parent 6ad74bb991
commit 344e10ac46
4 changed files with 92 additions and 1 deletions
+2
View File
@@ -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
View File
@@ -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": [
+12
View File
@@ -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();
});
});
+77
View File
@@ -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
};