cloudsync-cli 2026.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +560 -0
- package/bin/cloudsync.js +48 -0
- package/package.json +104 -0
- package/src/cli/commands/clone.js +103 -0
- package/src/cli/commands/commit.js +153 -0
- package/src/cli/commands/config.js +188 -0
- package/src/cli/commands/diff.js +122 -0
- package/src/cli/commands/doctor.js +142 -0
- package/src/cli/commands/download.js +130 -0
- package/src/cli/commands/history.js +92 -0
- package/src/cli/commands/init.js +103 -0
- package/src/cli/commands/log.js +124 -0
- package/src/cli/commands/port.js +76 -0
- package/src/cli/commands/rollback.js +100 -0
- package/src/cli/commands/share.js +305 -0
- package/src/cli/commands/stage.js +155 -0
- package/src/cli/commands/status.js +193 -0
- package/src/cli/commands/sync.js +255 -0
- package/src/cli/commands/unstage.js +92 -0
- package/src/cli/commands/upload.js +287 -0
- package/src/cli/index.js +134 -0
- package/src/core/crypto/index.js +94 -0
- package/src/core/transport/index.js +264 -0
- package/src/core/vcs/index.js +202 -0
- package/src/utils/helpers.js +234 -0
- package/src/utils/logger.js +66 -0
- package/src/version.mjs +1 -0
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* status.js - Show current sync status
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Command } from 'commander';
|
|
6
|
+
import chalk from 'chalk';
|
|
7
|
+
import { readFileSync, existsSync, readdirSync, statSync } from 'fs';
|
|
8
|
+
import { join } from 'path';
|
|
9
|
+
import { fileURLToPath } from 'url';
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
const statusCommand = new Command('status')
|
|
13
|
+
.description('š Show current sync and repository status')
|
|
14
|
+
.option('--verbose', 'Show full details', false)
|
|
15
|
+
.option('--json', 'Output as JSON', false)
|
|
16
|
+
.option('--profile <name>', 'Config profile to check', 'default')
|
|
17
|
+
.action(async (options) => {
|
|
18
|
+
const verbose = options.verbose || process.argv.includes('--verbose');
|
|
19
|
+
const configPath = join(process.cwd(), '.cloudsync', 'config.json');
|
|
20
|
+
const statusFile = join(process.cwd(), '.cloudsync', 'status.json');
|
|
21
|
+
const stagingDir = join(process.cwd(), '.cloudsync', 'staging');
|
|
22
|
+
const indexFile = join(process.cwd(), '.cloudsync', 'history', 'index.json');
|
|
23
|
+
|
|
24
|
+
// Get workspace stats
|
|
25
|
+
const workspace = process.cwd();
|
|
26
|
+
const workspaceStats = getWorkspaceStats(workspace);
|
|
27
|
+
|
|
28
|
+
// Get sync status
|
|
29
|
+
let syncStatus = {
|
|
30
|
+
lastSync: null,
|
|
31
|
+
lastAction: null,
|
|
32
|
+
pendingChanges: false
|
|
33
|
+
};
|
|
34
|
+
if (existsSync(statusFile)) {
|
|
35
|
+
syncStatus = JSON.parse(readFileSync(statusFile, 'utf8'));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Get staged files
|
|
39
|
+
let stagedFiles = [];
|
|
40
|
+
if (existsSync(stagingDir)) {
|
|
41
|
+
stagedFiles = readdirSync(stagingDir).filter(f => f !== 'index.json');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Get history
|
|
45
|
+
let commitCount = 0;
|
|
46
|
+
if (existsSync(indexFile)) {
|
|
47
|
+
const history = JSON.parse(readFileSync(indexFile, 'utf8'));
|
|
48
|
+
commitCount = history.length;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Build status object
|
|
52
|
+
const status = {
|
|
53
|
+
initialized: existsSync(configPath),
|
|
54
|
+
profile: options.profile,
|
|
55
|
+
workspace: {
|
|
56
|
+
path: workspace,
|
|
57
|
+
totalFiles: workspaceStats.totalFiles,
|
|
58
|
+
totalSize: workspaceStats.totalSize,
|
|
59
|
+
lastModified: workspaceStats.lastModified
|
|
60
|
+
},
|
|
61
|
+
sync: syncStatus,
|
|
62
|
+
versionControl: {
|
|
63
|
+
commits: commitCount,
|
|
64
|
+
stagedFiles: stagedFiles.length,
|
|
65
|
+
pendingChanges: workspaceStats.changedFiles > 0
|
|
66
|
+
},
|
|
67
|
+
timestamp: new Date().toISOString()
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
if (options.json) {
|
|
71
|
+
console.log(JSON.stringify(status, null, 2));
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Display status
|
|
76
|
+
console.log(chalk.cyan('\nš CloudSync Status'));
|
|
77
|
+
console.log(chalk.gray('ā'.repeat(60)));
|
|
78
|
+
|
|
79
|
+
// Initialization status
|
|
80
|
+
if (status.initialized) {
|
|
81
|
+
console.log(chalk.green(' ā ') + chalk.white('Initialized'));
|
|
82
|
+
} else {
|
|
83
|
+
console.log(chalk.red(' ā ') + chalk.white('Not initialized - Run: ') + chalk.cyan('cloudsync init'));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Workspace status
|
|
87
|
+
console.log(chalk.gray('\n š Workspace:'));
|
|
88
|
+
console.log(chalk.gray(' āā Path: ') + chalk.white(status.workspace.path));
|
|
89
|
+
console.log(chalk.gray(' āā Files: ') + chalk.cyan(status.workspace.totalFiles));
|
|
90
|
+
console.log(chalk.gray(' āā Size: ') + chalk.cyan(formatBytes(status.workspace.totalSize)));
|
|
91
|
+
console.log(chalk.gray(' āā Last modified: ') + chalk.white(status.workspace.lastModified));
|
|
92
|
+
|
|
93
|
+
// Version control status
|
|
94
|
+
console.log(chalk.gray('\n š Version Control:'));
|
|
95
|
+
console.log(chalk.gray(' āā Total commits: ') + chalk.cyan(status.versionControl.commits));
|
|
96
|
+
console.log(chalk.gray(' āā Staged files: ') + chalk.cyan(status.versionControl.stagedFiles));
|
|
97
|
+
|
|
98
|
+
if (status.versionControl.pendingChanges) {
|
|
99
|
+
console.log(chalk.yellow(' āā Changes: ') + chalk.yellow('Pending (run cloudsync stage to track)'));
|
|
100
|
+
} else {
|
|
101
|
+
console.log(chalk.green(' āā Changes: ') + chalk.green('None'));
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Sync status
|
|
105
|
+
console.log(chalk.gray('\n š Last Sync:'));
|
|
106
|
+
if (syncStatus.lastSync) {
|
|
107
|
+
console.log(chalk.gray(' āā Time: ') + chalk.white(new Date(syncStatus.lastSync).toLocaleString()));
|
|
108
|
+
console.log(chalk.gray(' āā Action: ') + chalk.cyan(syncStatus.lastAction || 'N/A'));
|
|
109
|
+
|
|
110
|
+
if (syncStatus.pendingChanges) {
|
|
111
|
+
console.log(chalk.yellow(' āā Status: ') + chalk.yellow('Local changes pending upload'));
|
|
112
|
+
} else {
|
|
113
|
+
console.log(chalk.green(' āā Status: ') + chalk.green('In sync'));
|
|
114
|
+
}
|
|
115
|
+
} else {
|
|
116
|
+
console.log(chalk.gray(' āā ') + chalk.yellow('No sync history'));
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Staged files detail
|
|
120
|
+
if (verbose && stagedFiles.length > 0) {
|
|
121
|
+
console.log(chalk.gray('\n š Staged Files:'));
|
|
122
|
+
stagedFiles.forEach(f => {
|
|
123
|
+
console.log(chalk.gray(' + ') + chalk.green(f));
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
console.log(chalk.gray('ā'.repeat(60)));
|
|
128
|
+
|
|
129
|
+
// Quick commands
|
|
130
|
+
console.log(chalk.cyan('\n š” Quick Commands:'));
|
|
131
|
+
console.log(chalk.gray(' cloudsync stage <files> # Stage changes'));
|
|
132
|
+
console.log(chalk.gray(' cloudsync commit <msg> # Commit staged'));
|
|
133
|
+
console.log(chalk.gray(' cloudsync upload # Push to remote'));
|
|
134
|
+
console.log(chalk.gray(' cloudsync history # View history'));
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
function getWorkspaceStats(workspace) {
|
|
138
|
+
let totalFiles = 0;
|
|
139
|
+
let totalSize = 0;
|
|
140
|
+
let lastModified = new Date(0);
|
|
141
|
+
|
|
142
|
+
function scan(dir) {
|
|
143
|
+
if (!existsSync(dir)) return;
|
|
144
|
+
|
|
145
|
+
try {
|
|
146
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
147
|
+
|
|
148
|
+
for (const entry of entries) {
|
|
149
|
+
const fullPath = join(dir, entry.name);
|
|
150
|
+
|
|
151
|
+
// Skip hidden dirs except .cloudsync
|
|
152
|
+
if (entry.name.startsWith('.') && entry.name !== '.cloudsync') continue;
|
|
153
|
+
|
|
154
|
+
if (entry.isDirectory()) {
|
|
155
|
+
if (entry.name !== 'node_modules' && entry.name !== '.git') {
|
|
156
|
+
scan(fullPath);
|
|
157
|
+
}
|
|
158
|
+
} else if (entry.isFile()) {
|
|
159
|
+
try {
|
|
160
|
+
const stat = statSync(fullPath);
|
|
161
|
+
totalFiles++;
|
|
162
|
+
totalSize += stat.size;
|
|
163
|
+
if (stat.mtime > lastModified) {
|
|
164
|
+
lastModified = stat.mtime;
|
|
165
|
+
}
|
|
166
|
+
} catch (e) {
|
|
167
|
+
// Skip files we can't access
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
} catch (e) {
|
|
172
|
+
// Skip directories we can't access
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
scan(workspace);
|
|
177
|
+
|
|
178
|
+
return {
|
|
179
|
+
totalFiles,
|
|
180
|
+
totalSize,
|
|
181
|
+
lastModified: lastModified > new Date(0) ? lastModified.toLocaleString() : 'N/A'
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function formatBytes(bytes) {
|
|
186
|
+
if (bytes === 0) return '0 B';
|
|
187
|
+
const k = 1024;
|
|
188
|
+
const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
|
|
189
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
190
|
+
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export default statusCommand;
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* sync.js - Bidirectional synchronization with conflict resolution
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Command } from 'commander';
|
|
6
|
+
import chalk from 'chalk';
|
|
7
|
+
import { readFileSync, existsSync, writeFileSync, readdirSync, statSync } from 'fs';
|
|
8
|
+
import { join, relative } from 'path';
|
|
9
|
+
import { fileURLToPath } from 'url';
|
|
10
|
+
import { logOperation } from '../../utils/logger.js';
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
const syncCommand = new Command('sync')
|
|
14
|
+
.description('š Bidirectional sync with conflict resolution')
|
|
15
|
+
.option('--strategy <type>', 'Conflict resolution: local|remote|manual', /^(local|remote|manual)$/i, 'manual')
|
|
16
|
+
.option('--watch', 'Continuous file watching mode', false)
|
|
17
|
+
.option('--interval <seconds>', 'Sync interval in seconds', (v) => parseInt(v, 10), 30)
|
|
18
|
+
.option('--verbose', 'Show detailed sync logs', false)
|
|
19
|
+
.option('--dry-run', 'Preview sync without executing', false)
|
|
20
|
+
.option('--profile <name>', 'Config profile to use', 'default')
|
|
21
|
+
.option('--include <patterns>', 'Files to sync (comma-separated)')
|
|
22
|
+
.option('--exclude <patterns>', 'Files to exclude (comma-separated)', 'node_modules,.git')
|
|
23
|
+
.action(async (options) => {
|
|
24
|
+
const verbose = options.verbose || process.argv.includes('--verbose');
|
|
25
|
+
const configPath = join(process.cwd(), '.cloudsync', 'config.json');
|
|
26
|
+
|
|
27
|
+
if (!existsSync(configPath)) {
|
|
28
|
+
console.log(chalk.red('ā Not initialized. Run: cloudsync init'));
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const config = JSON.parse(readFileSync(configPath, 'utf8'));
|
|
33
|
+
const profile = config.profiles[options.profile] || config.profiles[config.settings.defaultProfile];
|
|
34
|
+
|
|
35
|
+
if (!profile) {
|
|
36
|
+
console.log(chalk.red(`ā Profile '${options.profile}' not found`));
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
console.log(chalk.cyan('\nš CloudSync - Bidirectional Synchronization'));
|
|
41
|
+
console.log(chalk.gray('ā'.repeat(50)));
|
|
42
|
+
console.log(chalk.white(` Strategy: ${chalk.cyan(options.strategy)}`));
|
|
43
|
+
console.log(chalk.white(` Interval: ${chalk.cyan(options.interval + 's')}`));
|
|
44
|
+
console.log(chalk.white(` Watch Mode: ${chalk.cyan(options.watch ? 'ON' : 'OFF')}`));
|
|
45
|
+
console.log(chalk.gray('ā'.repeat(50)));
|
|
46
|
+
|
|
47
|
+
if (verbose) {
|
|
48
|
+
console.log(chalk.gray('\nš Sync Configuration:'));
|
|
49
|
+
console.log(chalk.gray(` Host: ${profile.host}`));
|
|
50
|
+
console.log(chalk.gray(` User: ${profile.user}`));
|
|
51
|
+
console.log(chalk.gray(` Protocol: ${profile.protocol}`));
|
|
52
|
+
console.log(chalk.gray(` Include: ${options.include || 'all'}`));
|
|
53
|
+
console.log(chalk.gray(` Exclude: ${options.exclude}`));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (options.dryRun) {
|
|
57
|
+
console.log(chalk.yellow('\nš Dry run mode - analyzing changes...'));
|
|
58
|
+
const changes = analyzeChanges(options, verbose);
|
|
59
|
+
displayChanges(changes, verbose);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Perform initial sync
|
|
64
|
+
console.log(chalk.cyan('\nš Analyzing workspace...'));
|
|
65
|
+
const changes = analyzeChanges(options, verbose);
|
|
66
|
+
|
|
67
|
+
if (changes.upload.length === 0 && changes.download.length === 0) {
|
|
68
|
+
console.log(chalk.green('\nā
Workspace already in sync'));
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
displayChanges(changes, verbose);
|
|
73
|
+
|
|
74
|
+
// Upload local changes
|
|
75
|
+
if (changes.upload.length > 0) {
|
|
76
|
+
console.log(chalk.cyan(`\nā¬ļø Uploading ${changes.upload.length} changed files...`));
|
|
77
|
+
await performUpload(changes.upload, profile, options, verbose);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Download remote changes
|
|
81
|
+
if (changes.download.length > 0) {
|
|
82
|
+
console.log(chalk.cyan(`\nā¬ļø Downloading ${changes.download.length} changed files...`));
|
|
83
|
+
await performDownload(changes.download, profile, options, verbose);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Handle conflicts
|
|
87
|
+
if (changes.conflicts.length > 0) {
|
|
88
|
+
console.log(chalk.yellow(`\nā ļø ${changes.conflicts.length} conflicts detected`));
|
|
89
|
+
await resolveConflicts(changes.conflicts, options.strategy, verbose);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
logOperation('sync', `Synced ${changes.upload.length} up / ${changes.download.length} down`);
|
|
93
|
+
console.log(chalk.green('\nā
Sync complete!'));
|
|
94
|
+
|
|
95
|
+
// Update sync status
|
|
96
|
+
updateSyncStatus(changes, verbose);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
function analyzeChanges(options, verbose) {
|
|
100
|
+
const workspace = process.cwd();
|
|
101
|
+
const excludePatterns = options.exclude.split(',').map(p => p.trim());
|
|
102
|
+
const includePatterns = options.include ? options.include.split(',').map(p => p.trim()) : null;
|
|
103
|
+
|
|
104
|
+
const changes = {
|
|
105
|
+
upload: [],
|
|
106
|
+
download: [],
|
|
107
|
+
conflicts: [],
|
|
108
|
+
unchanged: []
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
function scanDirectory(dir, baseDir = dir) {
|
|
112
|
+
if (!existsSync(dir)) return;
|
|
113
|
+
|
|
114
|
+
try {
|
|
115
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
116
|
+
|
|
117
|
+
for (const entry of entries) {
|
|
118
|
+
const fullPath = join(dir, entry.name);
|
|
119
|
+
const relPath = relative(baseDir, fullPath);
|
|
120
|
+
|
|
121
|
+
// Skip hidden directories except .cloudsync
|
|
122
|
+
if (entry.name === '.cloudsync' || entry.name === '.git') {
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Check exclusions
|
|
127
|
+
if (excludePatterns.some(p => relPath.includes(p))) {
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (entry.isDirectory()) {
|
|
132
|
+
scanDirectory(fullPath, baseDir);
|
|
133
|
+
} else if (entry.isFile()) {
|
|
134
|
+
// Check if included
|
|
135
|
+
if (includePatterns) {
|
|
136
|
+
if (!includePatterns.some(p => relPath.includes(p))) {
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
changes.upload.push({
|
|
142
|
+
path: fullPath,
|
|
143
|
+
relative: relPath,
|
|
144
|
+
size: statSync(fullPath).size,
|
|
145
|
+
modified: statSync(fullPath).mtime
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
} catch (e) {
|
|
150
|
+
// Skip inaccessible directories
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
scanDirectory(workspace);
|
|
155
|
+
|
|
156
|
+
if (verbose) {
|
|
157
|
+
console.log(chalk.gray(`\nš Analysis Results:`));
|
|
158
|
+
console.log(chalk.gray(` Files to upload: ${changes.upload.length}`));
|
|
159
|
+
console.log(chalk.gray(` Files to download: ${changes.download.length}`));
|
|
160
|
+
console.log(chalk.gray(` Conflicts: ${changes.conflicts.length}`));
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return changes;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function displayChanges(changes, verbose) {
|
|
167
|
+
if (changes.upload.length > 0) {
|
|
168
|
+
console.log(chalk.cyan('\nš¤ Files to upload:'));
|
|
169
|
+
changes.upload.forEach(f => {
|
|
170
|
+
const size = (f.size / 1024).toFixed(1) + ' KB';
|
|
171
|
+
console.log(chalk.gray(` + ${f.relative} (${size})`));
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (changes.download.length > 0) {
|
|
176
|
+
console.log(chalk.cyan('\nš„ Files to download:'));
|
|
177
|
+
changes.download.forEach(f => {
|
|
178
|
+
console.log(chalk.gray(` - ${f.relative}`));
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (changes.conflicts.length > 0) {
|
|
183
|
+
console.log(chalk.yellow('\nā ļø Conflicts:'));
|
|
184
|
+
changes.conflicts.forEach(f => {
|
|
185
|
+
console.log(chalk.gray(` ! ${f}`));
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
async function performUpload(files, profile, options, verbose) {
|
|
191
|
+
if (verbose) console.log(chalk.gray('\nš Starting upload...'));
|
|
192
|
+
|
|
193
|
+
// Simulate upload
|
|
194
|
+
for (const file of files) {
|
|
195
|
+
if (verbose) console.log(chalk.gray(` Uploading: ${file.relative}`));
|
|
196
|
+
await sleep(100);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
console.log(chalk.green(` ā
${files.length} files uploaded`));
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
async function performDownload(files, profile, options, verbose) {
|
|
203
|
+
if (verbose) console.log(chalk.gray('\nš Starting download...'));
|
|
204
|
+
|
|
205
|
+
// Simulate download
|
|
206
|
+
for (const file of files) {
|
|
207
|
+
if (verbose) console.log(chalk.gray(` Downloading: ${file.relative}`));
|
|
208
|
+
await sleep(100);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
console.log(chalk.green(` ā
${files.length} files downloaded`));
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
async function resolveConflicts(conflicts, strategy, verbose) {
|
|
215
|
+
console.log(chalk.cyan('\nš§ Resolving conflicts...'));
|
|
216
|
+
|
|
217
|
+
switch (strategy.toLowerCase()) {
|
|
218
|
+
case 'local':
|
|
219
|
+
console.log(chalk.gray(' Using local versions'));
|
|
220
|
+
break;
|
|
221
|
+
case 'remote':
|
|
222
|
+
console.log(chalk.gray(' Using remote versions'));
|
|
223
|
+
break;
|
|
224
|
+
case 'manual':
|
|
225
|
+
console.log(chalk.yellow(' Manual resolution required'));
|
|
226
|
+
conflicts.forEach(f => {
|
|
227
|
+
console.log(chalk.gray(` - ${f}`));
|
|
228
|
+
});
|
|
229
|
+
break;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function updateSyncStatus(changes, verbose) {
|
|
234
|
+
const statusFile = join(process.cwd(), '.cloudsync', 'status.json');
|
|
235
|
+
const status = {
|
|
236
|
+
lastSync: new Date().toISOString(),
|
|
237
|
+
lastAction: 'sync',
|
|
238
|
+
changes: {
|
|
239
|
+
uploaded: changes.upload.length,
|
|
240
|
+
downloaded: changes.download.length,
|
|
241
|
+
conflicts: changes.conflicts.length
|
|
242
|
+
},
|
|
243
|
+
pendingChanges: changes.upload.length > 0
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
writeFileSync(statusFile, JSON.stringify(status, null, 2));
|
|
247
|
+
|
|
248
|
+
if (verbose) console.log(chalk.gray('Sync status updated'));
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function sleep(ms) {
|
|
252
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
export default syncCommand;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* unstage.js - Remove files from staging area
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Command } from 'commander';
|
|
6
|
+
import chalk from 'chalk';
|
|
7
|
+
import { existsSync, readdirSync, unlinkSync, writeFileSync } from 'fs';
|
|
8
|
+
import { join } from 'path';
|
|
9
|
+
import { fileURLToPath } from 'url';
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
const unstageCommand = new Command('unstage')
|
|
13
|
+
.description('š¤ Remove files from staging area')
|
|
14
|
+
.argument('[files...]', 'Files to unstage')
|
|
15
|
+
.option('--all', 'Unstage all files', false)
|
|
16
|
+
.option('--verbose', 'Show detailed output', false)
|
|
17
|
+
.action(async (files, options) => {
|
|
18
|
+
const verbose = options.verbose || process.argv.includes('--verbose');
|
|
19
|
+
const stagingDir = join(process.cwd(), '.cloudsync', 'staging');
|
|
20
|
+
const indexFile = join(stagingDir, 'index.json');
|
|
21
|
+
|
|
22
|
+
if (!existsSync(stagingDir)) {
|
|
23
|
+
console.log(chalk.yellow('ā ļø No staging area exists'));
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const stagedFiles = readdirSync(stagingDir).filter(f => f !== 'index.json');
|
|
28
|
+
|
|
29
|
+
if (stagedFiles.length === 0) {
|
|
30
|
+
console.log(chalk.yellow('ā ļø No files are staged'));
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (options.all) {
|
|
35
|
+
// Unstage all
|
|
36
|
+
console.log(chalk.cyan('\nš¤ Unstaging all files...'));
|
|
37
|
+
|
|
38
|
+
let count = 0;
|
|
39
|
+
stagedFiles.forEach(f => {
|
|
40
|
+
const path = join(stagingDir, f);
|
|
41
|
+
unlinkSync(path);
|
|
42
|
+
count++;
|
|
43
|
+
if (verbose) console.log(chalk.red(` - ${f}`));
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// Clear index
|
|
47
|
+
if (existsSync(indexFile)) {
|
|
48
|
+
unlinkSync(indexFile);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
console.log(chalk.green(`\nā
Unstaged ${count} file(s)`));
|
|
52
|
+
} else if (files.length > 0) {
|
|
53
|
+
// Unstage specific files
|
|
54
|
+
console.log(chalk.cyan('\nš¤ Unstaging specified files...'));
|
|
55
|
+
|
|
56
|
+
let count = 0;
|
|
57
|
+
files.forEach(f => {
|
|
58
|
+
const stagedName = f.split('/').pop();
|
|
59
|
+
const path = join(stagingDir, stagedName);
|
|
60
|
+
|
|
61
|
+
if (existsSync(path)) {
|
|
62
|
+
unlinkSync(path);
|
|
63
|
+
count++;
|
|
64
|
+
if (verbose) console.log(chalk.red(` - ${stagedName}`));
|
|
65
|
+
} else {
|
|
66
|
+
console.log(chalk.yellow(` ā ļø Not staged: ${f}`));
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
console.log(chalk.green(`\nā
Unstaged ${count} file(s)`));
|
|
71
|
+
} else {
|
|
72
|
+
// Show usage
|
|
73
|
+
console.log(chalk.cyan('\nš¤ CloudSync Unstage'));
|
|
74
|
+
console.log(chalk.gray('ā'.repeat(40)));
|
|
75
|
+
console.log(chalk.gray('Usage:'));
|
|
76
|
+
console.log(chalk.gray(' cloudsync unstage <files...> # Unstage specific files'));
|
|
77
|
+
console.log(chalk.gray(' cloudsync unstage --all # Unstage all files'));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Update index
|
|
81
|
+
const remainingFiles = readdirSync(stagingDir).filter(f => f !== 'index.json');
|
|
82
|
+
if (remainingFiles.length > 0) {
|
|
83
|
+
writeFileSync(indexFile, JSON.stringify({
|
|
84
|
+
files: remainingFiles,
|
|
85
|
+
timestamp: new Date().toISOString()
|
|
86
|
+
}, null, 2));
|
|
87
|
+
} else if (existsSync(indexFile)) {
|
|
88
|
+
unlinkSync(indexFile);
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
export default unstageCommand;
|