@koder-0x/scopy 1.0.0
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 +91 -0
- package/dist/commands/config.d.ts +3 -0
- package/dist/commands/config.d.ts.map +1 -0
- package/dist/commands/config.js +51 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/dest.d.ts +3 -0
- package/dist/commands/dest.d.ts.map +1 -0
- package/dist/commands/dest.js +54 -0
- package/dist/commands/dest.js.map +1 -0
- package/dist/commands/ghost.d.ts +4 -0
- package/dist/commands/ghost.d.ts.map +1 -0
- package/dist/commands/ghost.js +138 -0
- package/dist/commands/ghost.js.map +1 -0
- package/dist/commands/info.d.ts +4 -0
- package/dist/commands/info.d.ts.map +1 -0
- package/dist/commands/info.js +23 -0
- package/dist/commands/info.js.map +1 -0
- package/dist/commands/list.d.ts +3 -0
- package/dist/commands/list.d.ts.map +1 -0
- package/dist/commands/list.js +27 -0
- package/dist/commands/list.js.map +1 -0
- package/dist/commands/log.d.ts +3 -0
- package/dist/commands/log.d.ts.map +1 -0
- package/dist/commands/log.js +62 -0
- package/dist/commands/log.js.map +1 -0
- package/dist/commands/purge.d.ts +9 -0
- package/dist/commands/purge.d.ts.map +1 -0
- package/dist/commands/purge.js +62 -0
- package/dist/commands/purge.js.map +1 -0
- package/dist/commands/resync.d.ts +8 -0
- package/dist/commands/resync.d.ts.map +1 -0
- package/dist/commands/resync.js +218 -0
- package/dist/commands/resync.js.map +1 -0
- package/dist/commands/source.d.ts +3 -0
- package/dist/commands/source.d.ts.map +1 -0
- package/dist/commands/source.js +80 -0
- package/dist/commands/source.js.map +1 -0
- package/dist/commands/sync.d.ts +14 -0
- package/dist/commands/sync.d.ts.map +1 -0
- package/dist/commands/sync.js +288 -0
- package/dist/commands/sync.js.map +1 -0
- package/dist/config.d.ts +29 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +164 -0
- package/dist/config.js.map +1 -0
- package/dist/glob.d.ts +2 -0
- package/dist/glob.d.ts.map +1 -0
- package/dist/glob.js +5 -0
- package/dist/glob.js.map +1 -0
- package/dist/scopy.d.ts +3 -0
- package/dist/scopy.d.ts.map +1 -0
- package/dist/scopy.js +37 -0
- package/dist/scopy.js.map +1 -0
- package/dist/types.d.ts +47 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +7 -0
- package/dist/types.js.map +1 -0
- package/dist/ui.d.ts +12 -0
- package/dist/ui.d.ts.map +1 -0
- package/dist/ui.js +37 -0
- package/dist/ui.js.map +1 -0
- package/dist/validate.d.ts +6 -0
- package/dist/validate.d.ts.map +1 -0
- package/dist/validate.js +13 -0
- package/dist/validate.js.map +1 -0
- package/package.json +51 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"purge.js","sourceRoot":"","sources":["../../src/commands/purge.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Command } from 'commander';
|
|
2
|
+
export interface ResyncOptions {
|
|
3
|
+
dryRun: boolean;
|
|
4
|
+
unghost: boolean;
|
|
5
|
+
}
|
|
6
|
+
export declare function handleResync(dest: string, opts: ResyncOptions): Promise<void>;
|
|
7
|
+
export default function registerResync(program: Command): void;
|
|
8
|
+
//# sourceMappingURL=resync.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resync.d.ts","sourceRoot":"","sources":["../../src/commands/resync.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAczC,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,OAAO,CAAC;IAChB,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,wBAAsB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CA+NnF;AAED,MAAM,CAAC,OAAO,UAAU,cAAc,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAO7D"}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import { getSources, getDestinations, destinationExists, addCopy, getCopiesByDestination, fileCacheDir, fileCachePath, setGhosted, } from '../config.js';
|
|
5
|
+
import { error as uiError, dim } from '../ui.js';
|
|
6
|
+
export async function handleResync(dest, opts) {
|
|
7
|
+
const dryRun = opts.dryRun;
|
|
8
|
+
if (!destinationExists(dest)) {
|
|
9
|
+
uiError(`Destination "${dest}" is not registered`);
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
const destinations = getDestinations();
|
|
13
|
+
const destination = destinations.find((d) => d.name === dest);
|
|
14
|
+
if (destination === undefined) {
|
|
15
|
+
uiError(`Destination "${dest}" is not registered`);
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
const records = getCopiesByDestination(dest);
|
|
19
|
+
if (records.length === 0) {
|
|
20
|
+
dim(`No tracked files for "${dest}"`);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
const ghostedRecords = records.filter((r) => r.ghosted);
|
|
24
|
+
const activeRecords = records.filter((r) => !r.ghosted);
|
|
25
|
+
// --unghost: restore ghosted files from cache
|
|
26
|
+
if (opts.unghost) {
|
|
27
|
+
if (ghostedRecords.length === 0) {
|
|
28
|
+
dim('No ghosted files to restore');
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
if (dryRun) {
|
|
32
|
+
console.log(`Would restore ${ghostedRecords.length} ghosted file(s):`);
|
|
33
|
+
for (const record of ghostedRecords) {
|
|
34
|
+
console.log(chalk.dim(`· ${record.file}`));
|
|
35
|
+
}
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
let restored = 0;
|
|
39
|
+
let errs = 0;
|
|
40
|
+
for (const record of ghostedRecords) {
|
|
41
|
+
if (record.index === undefined) {
|
|
42
|
+
uiError(`${record.file}: missing index, cannot restore`);
|
|
43
|
+
errs++;
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
const cachePath = fileCachePath(dest, record.index);
|
|
47
|
+
if (!fs.existsSync(cachePath)) {
|
|
48
|
+
uiError(`${record.file}: cache not found, cannot restore`);
|
|
49
|
+
errs++;
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
const destPath = path.join(destination.location, record.file);
|
|
53
|
+
fs.mkdirSync(path.dirname(destPath), { recursive: true });
|
|
54
|
+
fs.copyFileSync(cachePath, destPath);
|
|
55
|
+
setGhosted(dest, record.index, false);
|
|
56
|
+
addCopy({
|
|
57
|
+
source: record.source,
|
|
58
|
+
destination: dest,
|
|
59
|
+
file: record.file,
|
|
60
|
+
copiedAt: new Date().toISOString(),
|
|
61
|
+
});
|
|
62
|
+
console.log(`${chalk.green('✓')} ${record.file} (restored from cache)`);
|
|
63
|
+
restored++;
|
|
64
|
+
}
|
|
65
|
+
const restoredStr = chalk.green(String(restored));
|
|
66
|
+
const errStr = errs > 0 ? chalk.red(`${errs} error(s)`) : `${errs} error(s)`;
|
|
67
|
+
console.log(`${restoredStr} restored, ${errStr}`);
|
|
68
|
+
if (errs > 0)
|
|
69
|
+
process.exitCode = 1;
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
// Group records by source name
|
|
73
|
+
const bySource = new Map();
|
|
74
|
+
for (const record of activeRecords) {
|
|
75
|
+
const group = bySource.get(record.source);
|
|
76
|
+
if (group !== undefined) {
|
|
77
|
+
group.push(record);
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
bySource.set(record.source, [record]);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
const sources = getSources();
|
|
84
|
+
if (dryRun) {
|
|
85
|
+
// Collect all valid files first, then list
|
|
86
|
+
const validFiles = [];
|
|
87
|
+
for (const [sourceName, group] of bySource) {
|
|
88
|
+
const source = sources.find((s) => s.name === sourceName);
|
|
89
|
+
if (source === undefined) {
|
|
90
|
+
for (const record of group) {
|
|
91
|
+
uiError(`${record.file}: source "${sourceName}" is no longer registered`);
|
|
92
|
+
}
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
if (source.type === 'git') {
|
|
96
|
+
for (const record of group) {
|
|
97
|
+
validFiles.push(record.file);
|
|
98
|
+
}
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
const workTree = source.location;
|
|
102
|
+
for (const record of group) {
|
|
103
|
+
const srcPath = path.join(workTree, record.sourcePath ?? record.file);
|
|
104
|
+
if (!fs.existsSync(srcPath)) {
|
|
105
|
+
uiError(`${record.file}: file not found in source`);
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
validFiles.push(record.file);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
console.log(`Would copy ${validFiles.length} file(s)`);
|
|
112
|
+
for (const file of validFiles) {
|
|
113
|
+
console.log(chalk.dim(`· ${file}`));
|
|
114
|
+
}
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
let copied = 0;
|
|
118
|
+
let errors = 0;
|
|
119
|
+
for (const [sourceName, group] of bySource) {
|
|
120
|
+
const source = sources.find((s) => s.name === sourceName);
|
|
121
|
+
if (source === undefined) {
|
|
122
|
+
for (const record of group) {
|
|
123
|
+
uiError(`${record.file}: source "${sourceName}" is no longer registered`);
|
|
124
|
+
errors++;
|
|
125
|
+
}
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
if (source.type === 'git') {
|
|
129
|
+
// Parse owner/repo from stored location (https://github.com/{owner}/{repo})
|
|
130
|
+
const urlParts = new URL(source.location).pathname.split('/').filter(Boolean);
|
|
131
|
+
const owner = urlParts[0];
|
|
132
|
+
const repo = urlParts[1];
|
|
133
|
+
const subPath = source.path ? source.path.replace(/^\//, '') : '';
|
|
134
|
+
const headers = { Accept: 'application/vnd.github+json', 'X-GitHub-Api-Version': '2022-11-28' };
|
|
135
|
+
for (const record of group) {
|
|
136
|
+
const fileRelPath = record.sourcePath ?? record.file;
|
|
137
|
+
const filePath = subPath ? `${subPath}/${fileRelPath}` : fileRelPath;
|
|
138
|
+
try {
|
|
139
|
+
const metaRes = await fetch(`https://api.github.com/repos/${owner}/${repo}/contents/${filePath}`, { headers });
|
|
140
|
+
if (!metaRes.ok) {
|
|
141
|
+
uiError(`${record.file}: HTTP ${metaRes.status}`);
|
|
142
|
+
errors++;
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
const meta = await metaRes.json();
|
|
146
|
+
if (typeof meta.download_url !== 'string') {
|
|
147
|
+
uiError(`${record.file}: no download_url in API response`);
|
|
148
|
+
errors++;
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
const res = await fetch(meta.download_url);
|
|
152
|
+
if (!res.ok) {
|
|
153
|
+
uiError(`${record.file}: HTTP ${res.status}`);
|
|
154
|
+
errors++;
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
const content = Buffer.from(await res.arrayBuffer());
|
|
158
|
+
const destPath = path.join(destination.location, record.file);
|
|
159
|
+
fs.mkdirSync(path.dirname(destPath), { recursive: true });
|
|
160
|
+
fs.writeFileSync(destPath, content);
|
|
161
|
+
if (record.index !== undefined) {
|
|
162
|
+
fs.mkdirSync(fileCacheDir(dest), { recursive: true });
|
|
163
|
+
fs.writeFileSync(fileCachePath(dest, record.index), content);
|
|
164
|
+
}
|
|
165
|
+
const resolvedSourcePath = subPath && filePath.startsWith(subPath + '/') ? filePath.slice(subPath.length + 1) : filePath;
|
|
166
|
+
addCopy({ source: sourceName, destination: dest, file: record.file, sourcePath: resolvedSourcePath, copiedAt: new Date().toISOString() });
|
|
167
|
+
console.log(`${chalk.green('✓')} ${record.file}`);
|
|
168
|
+
copied++;
|
|
169
|
+
}
|
|
170
|
+
catch (err) {
|
|
171
|
+
uiError(`${record.file}: ${err instanceof Error ? err.message : String(err)}`);
|
|
172
|
+
errors++;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
continue; // next source group
|
|
176
|
+
}
|
|
177
|
+
// Local source
|
|
178
|
+
const workTree = source.location;
|
|
179
|
+
for (const record of group) {
|
|
180
|
+
const srcPath = path.join(workTree, record.sourcePath ?? record.file);
|
|
181
|
+
if (!fs.existsSync(srcPath)) {
|
|
182
|
+
uiError(`${record.file}: file not found in source`);
|
|
183
|
+
errors++;
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
const destPath = path.join(destination.location, record.file);
|
|
187
|
+
fs.mkdirSync(path.dirname(destPath), { recursive: true });
|
|
188
|
+
fs.copyFileSync(srcPath, destPath);
|
|
189
|
+
if (record.index !== undefined) {
|
|
190
|
+
fs.mkdirSync(fileCacheDir(dest), { recursive: true });
|
|
191
|
+
fs.copyFileSync(srcPath, fileCachePath(dest, record.index));
|
|
192
|
+
}
|
|
193
|
+
addCopy({
|
|
194
|
+
source: sourceName,
|
|
195
|
+
destination: dest,
|
|
196
|
+
file: record.file,
|
|
197
|
+
copiedAt: new Date().toISOString(),
|
|
198
|
+
});
|
|
199
|
+
console.log(`${chalk.green('✓')} ${record.file}`);
|
|
200
|
+
copied++;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
const copiedStr = chalk.green(String(copied));
|
|
204
|
+
const errorStr = errors > 0 ? chalk.red(`${errors} error(s)`) : `${errors} error(s)`;
|
|
205
|
+
console.log(`${copiedStr} copied, ${errorStr}`);
|
|
206
|
+
if (errors > 0) {
|
|
207
|
+
process.exitCode = 1;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
export default function registerResync(program) {
|
|
211
|
+
program
|
|
212
|
+
.command('resync <dest>')
|
|
213
|
+
.description('Re-copy all tracked files to a destination')
|
|
214
|
+
.option('--dry-run', 'Preview what would be copied without making changes')
|
|
215
|
+
.option('--unghost', 'Restore ghosted files from cache')
|
|
216
|
+
.action(handleResync);
|
|
217
|
+
}
|
|
218
|
+
//# sourceMappingURL=resync.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resync.js","sourceRoot":"","sources":["../../src/commands/resync.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"source.d.ts","sourceRoot":"","sources":["../../src/commands/source.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA8EzC,MAAM,CAAC,OAAO,UAAU,QAAQ,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAsBvD"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { getSources, addSource, removeSource, sourceExists } from '../config.js';
|
|
3
|
+
import { validateLocalPath } from '../validate.js';
|
|
4
|
+
import { success, error, dim, printSourceList } from '../ui.js';
|
|
5
|
+
function parseLocation(location) {
|
|
6
|
+
if (location.startsWith('https://')) {
|
|
7
|
+
const url = new URL(location);
|
|
8
|
+
if (url.host !== 'github.com') {
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
const segments = url.pathname.split('/').filter(Boolean);
|
|
12
|
+
if (segments.length < 2) {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
const baseUrl = `${url.protocol}//${url.host}/${segments[0]}/${segments[1]}`;
|
|
16
|
+
const subPath = segments.length > 2 ? '/' + segments.slice(2).join('/') : '';
|
|
17
|
+
return { type: 'git', baseUrl, subPath };
|
|
18
|
+
}
|
|
19
|
+
return { type: 'local' };
|
|
20
|
+
}
|
|
21
|
+
async function handleAdd(name, location) {
|
|
22
|
+
if (sourceExists(name)) {
|
|
23
|
+
error(`source "${name}" already exists`);
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const parsed = parseLocation(location);
|
|
27
|
+
if (!parsed) {
|
|
28
|
+
error(`invalid location: "${location}"`);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
if (parsed.type === 'git') {
|
|
32
|
+
addSource({ type: 'git', name, location: parsed.baseUrl, path: parsed.subPath || undefined });
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
const result = validateLocalPath(location);
|
|
36
|
+
if (!result.valid) {
|
|
37
|
+
error(result.error ?? 'invalid local path');
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
addSource({ type: 'local', name, location: path.resolve(location) });
|
|
41
|
+
}
|
|
42
|
+
success(`Source "${name}" added`);
|
|
43
|
+
}
|
|
44
|
+
function handleRemove(name) {
|
|
45
|
+
if (!sourceExists(name)) {
|
|
46
|
+
error(`source "${name}" not found`);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
removeSource(name);
|
|
50
|
+
success(`Source "${name}" removed`);
|
|
51
|
+
}
|
|
52
|
+
function handleList() {
|
|
53
|
+
const sources = getSources();
|
|
54
|
+
if (sources.length === 0) {
|
|
55
|
+
dim('No sources registered');
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
printSourceList(sources);
|
|
59
|
+
}
|
|
60
|
+
export default function register(program) {
|
|
61
|
+
const source = program
|
|
62
|
+
.command('source')
|
|
63
|
+
.description('Manage asset sources');
|
|
64
|
+
source
|
|
65
|
+
.command('add')
|
|
66
|
+
.description('Add a source (git URL or local path)')
|
|
67
|
+
.argument('<name>', 'Source name')
|
|
68
|
+
.argument('<location>', 'Git URL or local filesystem path')
|
|
69
|
+
.action(handleAdd);
|
|
70
|
+
source
|
|
71
|
+
.command('remove')
|
|
72
|
+
.description('Remove a source by name')
|
|
73
|
+
.argument('<name>', 'Source name')
|
|
74
|
+
.action(handleRemove);
|
|
75
|
+
source
|
|
76
|
+
.command('list')
|
|
77
|
+
.description('List all registered sources')
|
|
78
|
+
.action(handleList);
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=source.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"source.js","sourceRoot":"","sources":["../../src/commands/source.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Command } from 'commander';
|
|
2
|
+
interface GitHubFile {
|
|
3
|
+
name: string;
|
|
4
|
+
relativePath: string;
|
|
5
|
+
downloadUrl: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function fetchGitHubFiles(owner: string, repo: string, subPath: string, fileSpec: string | undefined): Promise<GitHubFile[]>;
|
|
8
|
+
export declare function handleSync(sourceSpec: string, destName: string, options: {
|
|
9
|
+
force?: boolean;
|
|
10
|
+
dryRun?: boolean;
|
|
11
|
+
}): Promise<void>;
|
|
12
|
+
export default function register(program: Command): void;
|
|
13
|
+
export {};
|
|
14
|
+
//# sourceMappingURL=sync.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../../src/commands/sync.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA0BzC,UAAU,UAAU;IAClB,IAAI,EAAE,MAAM,CAAA;IACZ,YAAY,EAAE,MAAM,CAAA;IACpB,WAAW,EAAE,MAAM,CAAA;CACpB;AAED,wBAAsB,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAwDxI;AAmDD,wBAAsB,UAAU,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CA2LpI;AAED,MAAM,CAAC,OAAO,UAAU,QAAQ,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CASvD"}
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import checkbox from '@inquirer/checkbox';
|
|
5
|
+
import { getSources, sourceExists, getDestinations, destinationExists, addCopy, getCopiesByDestination, fileCacheDir, fileCachePath, getPref, isTipDismissed, } from '../config.js';
|
|
6
|
+
import { error as uiError, dim } from '../ui.js';
|
|
7
|
+
import { globPattern } from '../glob.js';
|
|
8
|
+
async function selectOverwrites(names) {
|
|
9
|
+
if (names.length === 0)
|
|
10
|
+
return new Set();
|
|
11
|
+
const chosen = await checkbox({
|
|
12
|
+
message: 'Select files to overwrite',
|
|
13
|
+
choices: names.map((n) => ({ name: n, value: n, checked: true })),
|
|
14
|
+
theme: { style: { answer: () => '' } },
|
|
15
|
+
});
|
|
16
|
+
return new Set(chosen);
|
|
17
|
+
}
|
|
18
|
+
export async function fetchGitHubFiles(owner, repo, subPath, fileSpec) {
|
|
19
|
+
const dirPath = subPath ? subPath.replace(/^\//, '') : '';
|
|
20
|
+
let listPath;
|
|
21
|
+
let globPart;
|
|
22
|
+
if (fileSpec !== undefined && fileSpec.includes('*')) {
|
|
23
|
+
// glob: list the directory part, filter by glob
|
|
24
|
+
const lastSlash = fileSpec.lastIndexOf('/');
|
|
25
|
+
const dirPart = lastSlash === -1 ? '' : fileSpec.slice(0, lastSlash);
|
|
26
|
+
globPart = fileSpec.slice(lastSlash + 1);
|
|
27
|
+
listPath = [dirPath, dirPart].filter(Boolean).join('/');
|
|
28
|
+
}
|
|
29
|
+
else if (fileSpec !== undefined) {
|
|
30
|
+
// specific file: listPath is the file itself
|
|
31
|
+
listPath = [dirPath, fileSpec].filter(Boolean).join('/');
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
// all files in subPath dir
|
|
35
|
+
listPath = dirPath;
|
|
36
|
+
}
|
|
37
|
+
const apiUrl = `https://api.github.com/repos/${owner}/${repo}/contents/${listPath}`;
|
|
38
|
+
const res = await fetch(apiUrl, { headers: { Accept: 'application/vnd.github+json', 'X-GitHub-Api-Version': '2022-11-28' } });
|
|
39
|
+
if (!res.ok) {
|
|
40
|
+
throw new Error(`GitHub API error ${res.status} for ${apiUrl}`);
|
|
41
|
+
}
|
|
42
|
+
const data = await res.json();
|
|
43
|
+
// Single file response
|
|
44
|
+
if (typeof data === 'object' && data !== null && !Array.isArray(data)) {
|
|
45
|
+
const obj = data;
|
|
46
|
+
if (obj.type === 'file' && typeof obj.name === 'string' && typeof obj.download_url === 'string') {
|
|
47
|
+
const name = fileSpec ?? obj.name;
|
|
48
|
+
return [{ name, relativePath: name, downloadUrl: obj.download_url }];
|
|
49
|
+
}
|
|
50
|
+
// directory object — shouldn't happen for file spec
|
|
51
|
+
return [];
|
|
52
|
+
}
|
|
53
|
+
if (!Array.isArray(data))
|
|
54
|
+
return [];
|
|
55
|
+
let entries = data.filter((e) => e.type === 'file');
|
|
56
|
+
if (globPart !== undefined) {
|
|
57
|
+
const regex = globPattern(globPart);
|
|
58
|
+
entries = entries.filter((e) => typeof e.name === 'string' && regex.test(e.name));
|
|
59
|
+
}
|
|
60
|
+
// Compute the subdir prefix so callers can reconstruct the full source-relative path
|
|
61
|
+
const dirPrefix = globPart !== undefined
|
|
62
|
+
? (fileSpec !== undefined ? fileSpec.slice(0, fileSpec.lastIndexOf('/') + 1) : '')
|
|
63
|
+
: (fileSpec !== undefined ? `${fileSpec}/` : '');
|
|
64
|
+
return entries
|
|
65
|
+
.filter((e) => typeof e.name === 'string' && typeof e.download_url === 'string')
|
|
66
|
+
.map((e) => ({ name: e.name, relativePath: `${dirPrefix}${e.name}`, downloadUrl: e.download_url }));
|
|
67
|
+
}
|
|
68
|
+
function resolveFiles(workTree, fileSpec) {
|
|
69
|
+
if (fileSpec === undefined) {
|
|
70
|
+
return fs.readdirSync(workTree, { withFileTypes: true })
|
|
71
|
+
.filter((e) => e.isFile())
|
|
72
|
+
.map((e) => ({ src: path.join(workTree, e.name), rel: e.name, sourcePath: e.name }));
|
|
73
|
+
}
|
|
74
|
+
if (fileSpec.includes('*')) {
|
|
75
|
+
const lastSlash = fileSpec.lastIndexOf('/');
|
|
76
|
+
const dirPart = lastSlash === -1 ? '' : fileSpec.slice(0, lastSlash);
|
|
77
|
+
const globPart = fileSpec.slice(lastSlash + 1);
|
|
78
|
+
const regex = globPattern(globPart);
|
|
79
|
+
const dirPath = path.join(workTree, dirPart);
|
|
80
|
+
const resolvedDir = path.resolve(dirPath);
|
|
81
|
+
const baseRes = path.resolve(workTree);
|
|
82
|
+
if (resolvedDir !== baseRes && !resolvedDir.startsWith(baseRes + path.sep)) {
|
|
83
|
+
throw new Error(`fileSpec escapes base directory: ${fileSpec}`);
|
|
84
|
+
}
|
|
85
|
+
if (!fs.existsSync(dirPath)) {
|
|
86
|
+
return [];
|
|
87
|
+
}
|
|
88
|
+
return fs.readdirSync(dirPath, { withFileTypes: true })
|
|
89
|
+
.filter((e) => e.isFile() && regex.test(e.name))
|
|
90
|
+
.map((e) => ({
|
|
91
|
+
src: path.join(dirPath, e.name),
|
|
92
|
+
rel: e.name,
|
|
93
|
+
sourcePath: dirPart ? `${dirPart}/${e.name}` : e.name,
|
|
94
|
+
}));
|
|
95
|
+
}
|
|
96
|
+
// Specific file path
|
|
97
|
+
const srcPath = path.join(workTree, fileSpec);
|
|
98
|
+
const resolvedSrc = path.resolve(srcPath);
|
|
99
|
+
if (!resolvedSrc.startsWith(path.resolve(workTree) + path.sep)) {
|
|
100
|
+
throw new Error(`fileSpec escapes base directory: ${fileSpec}`);
|
|
101
|
+
}
|
|
102
|
+
if (!fs.existsSync(srcPath)) {
|
|
103
|
+
return [];
|
|
104
|
+
}
|
|
105
|
+
if (fs.statSync(srcPath).isDirectory()) {
|
|
106
|
+
return fs.readdirSync(srcPath, { withFileTypes: true })
|
|
107
|
+
.filter((e) => e.isFile())
|
|
108
|
+
.map((e) => ({ src: path.join(srcPath, e.name), rel: e.name, sourcePath: `${fileSpec}/${e.name}` }));
|
|
109
|
+
}
|
|
110
|
+
return [{ src: srcPath, rel: fileSpec, sourcePath: fileSpec }];
|
|
111
|
+
}
|
|
112
|
+
export async function handleSync(sourceSpec, destName, options) {
|
|
113
|
+
const { dryRun } = options;
|
|
114
|
+
const force = (options.force ?? false) || getPref('sync.allowOverwrite');
|
|
115
|
+
// Parse source-spec
|
|
116
|
+
const slashIndex = sourceSpec.indexOf('/');
|
|
117
|
+
let sourceName;
|
|
118
|
+
let fileSpec;
|
|
119
|
+
if (slashIndex === -1) {
|
|
120
|
+
sourceName = sourceSpec;
|
|
121
|
+
fileSpec = undefined;
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
sourceName = sourceSpec.slice(0, slashIndex);
|
|
125
|
+
fileSpec = sourceSpec.slice(slashIndex + 1);
|
|
126
|
+
}
|
|
127
|
+
// Validate
|
|
128
|
+
if (!sourceExists(sourceName)) {
|
|
129
|
+
uiError(`source "${sourceName}" not found`);
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
if (!destinationExists(destName)) {
|
|
133
|
+
uiError(`destination "${destName}" not found`);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
const source = getSources().find((s) => s.name === sourceName);
|
|
137
|
+
const dest = getDestinations().find((d) => d.name === destName);
|
|
138
|
+
if (source === undefined || dest === undefined) {
|
|
139
|
+
uiError('internal error: source or destination missing after validation');
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
if (!force && !dryRun && !isTipDismissed('sync.allowOverwrite')) {
|
|
143
|
+
console.log(chalk.dim('💡 you can run `scopy config sync.allowOverwrite true` to skip overwrite confirmation'));
|
|
144
|
+
}
|
|
145
|
+
if (source.type === 'git') {
|
|
146
|
+
// Parse owner/repo from stored location (https://github.com/{owner}/{repo})
|
|
147
|
+
const urlParts = new URL(source.location).pathname.split('/').filter(Boolean);
|
|
148
|
+
const owner = urlParts[0];
|
|
149
|
+
const repo = urlParts[1];
|
|
150
|
+
const subPath = source.path ?? '';
|
|
151
|
+
let gitFiles;
|
|
152
|
+
try {
|
|
153
|
+
gitFiles = await fetchGitHubFiles(owner, repo, subPath, fileSpec);
|
|
154
|
+
}
|
|
155
|
+
catch (err) {
|
|
156
|
+
uiError(`error fetching files from GitHub: ${err instanceof Error ? err.message : String(err)}`);
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
if (gitFiles.length === 0) {
|
|
160
|
+
dim('No files to copy');
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
if (dryRun) {
|
|
164
|
+
console.log(`${chalk.dim('Would copy')} ${gitFiles.length} file${gitFiles.length === 1 ? '' : 's'}:`);
|
|
165
|
+
for (const f of gitFiles) {
|
|
166
|
+
console.log(`${chalk.dim('·')} ${f.name}`);
|
|
167
|
+
}
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
let copied = 0;
|
|
171
|
+
let skipped = 0;
|
|
172
|
+
const copyErrors = [];
|
|
173
|
+
const conflicting = gitFiles.filter((f) => fs.existsSync(path.join(dest.location, f.name))).map((f) => f.name);
|
|
174
|
+
const toOverwrite = force ? new Set(conflicting) : await selectOverwrites(conflicting);
|
|
175
|
+
for (const f of gitFiles) {
|
|
176
|
+
const destPath = path.join(dest.location, f.name);
|
|
177
|
+
const existed = fs.existsSync(destPath);
|
|
178
|
+
if (existed && !toOverwrite.has(f.name)) {
|
|
179
|
+
skipped++;
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
try {
|
|
183
|
+
const contentRes = await fetch(f.downloadUrl);
|
|
184
|
+
if (!contentRes.ok)
|
|
185
|
+
throw new Error(`HTTP ${contentRes.status}`);
|
|
186
|
+
const content = Buffer.from(await contentRes.arrayBuffer());
|
|
187
|
+
fs.mkdirSync(path.dirname(destPath), { recursive: true });
|
|
188
|
+
fs.writeFileSync(destPath, content);
|
|
189
|
+
addCopy({ source: sourceName, destination: destName, file: f.name, sourcePath: f.relativePath, copiedAt: new Date().toISOString() });
|
|
190
|
+
const copies = getCopiesByDestination(destName);
|
|
191
|
+
const record = copies.find((c) => c.source === sourceName && c.file === f.name);
|
|
192
|
+
if (record && record.index !== undefined) {
|
|
193
|
+
fs.mkdirSync(fileCacheDir(destName), { recursive: true });
|
|
194
|
+
fs.writeFileSync(fileCachePath(destName, record.index), content);
|
|
195
|
+
}
|
|
196
|
+
console.log(`${chalk.green('✓')} ${f.name}${existed ? chalk.dim(' (overwritten)') : ''}`);
|
|
197
|
+
copied++;
|
|
198
|
+
}
|
|
199
|
+
catch (err) {
|
|
200
|
+
const fileErr = err instanceof Error ? err : new Error(String(err));
|
|
201
|
+
copyErrors.push({ file: f.name, err: fileErr });
|
|
202
|
+
uiError(`${f.name}: ${fileErr.message}`);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
console.log(`${chalk.green(String(copied))} copied, ${chalk.yellow(String(skipped))} skipped`);
|
|
206
|
+
if (copyErrors.length > 0) {
|
|
207
|
+
const noun = copyErrors.length === 1 ? 'error' : 'errors';
|
|
208
|
+
throw new Error(`${copyErrors.length} file ${noun} during sync:\n` +
|
|
209
|
+
copyErrors.map(e => ` ${e.file}: ${e.err.message}`).join('\n'));
|
|
210
|
+
}
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
// Local source
|
|
214
|
+
const workTree = source.location;
|
|
215
|
+
let files;
|
|
216
|
+
try {
|
|
217
|
+
files = resolveFiles(workTree, fileSpec);
|
|
218
|
+
}
|
|
219
|
+
catch (err) {
|
|
220
|
+
uiError(`error reading source files${fileSpec ? ` for "${fileSpec}"` : ''}: ${err instanceof Error ? err.message : String(err)}`);
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
if (files.length === 0) {
|
|
224
|
+
dim('No files to copy');
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
if (dryRun) {
|
|
228
|
+
console.log(`${chalk.dim('Would copy')} ${files.length} file${files.length === 1 ? '' : 's'}:`);
|
|
229
|
+
for (const f of files) {
|
|
230
|
+
console.log(`${chalk.dim('·')} ${f.rel}`);
|
|
231
|
+
}
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
let copied = 0;
|
|
235
|
+
let skipped = 0;
|
|
236
|
+
const copyErrors = [];
|
|
237
|
+
const conflicting = files.filter((f) => fs.existsSync(path.join(dest.location, f.rel))).map((f) => f.rel);
|
|
238
|
+
const toOverwrite = force ? new Set(conflicting) : await selectOverwrites(conflicting);
|
|
239
|
+
for (const f of files) {
|
|
240
|
+
const destPath = path.join(dest.location, f.rel);
|
|
241
|
+
const existed = fs.existsSync(destPath);
|
|
242
|
+
if (existed && !toOverwrite.has(f.rel)) {
|
|
243
|
+
skipped++;
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
try {
|
|
247
|
+
fs.mkdirSync(path.dirname(destPath), { recursive: true });
|
|
248
|
+
fs.copyFileSync(f.src, destPath);
|
|
249
|
+
addCopy({
|
|
250
|
+
source: sourceName,
|
|
251
|
+
destination: destName,
|
|
252
|
+
file: f.rel,
|
|
253
|
+
sourcePath: f.sourcePath,
|
|
254
|
+
copiedAt: new Date().toISOString(),
|
|
255
|
+
});
|
|
256
|
+
const copies = getCopiesByDestination(destName);
|
|
257
|
+
const record = copies.find((c) => c.source === sourceName && c.file === f.rel);
|
|
258
|
+
if (record && record.index !== undefined) {
|
|
259
|
+
fs.mkdirSync(fileCacheDir(destName), { recursive: true });
|
|
260
|
+
fs.copyFileSync(f.src, fileCachePath(destName, record.index));
|
|
261
|
+
}
|
|
262
|
+
console.log(`${chalk.green('✓')} ${f.rel}${existed ? chalk.dim(' (overwritten)') : ''}`);
|
|
263
|
+
copied++;
|
|
264
|
+
}
|
|
265
|
+
catch (err) {
|
|
266
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
267
|
+
copyErrors.push({ file: f.rel, err: error });
|
|
268
|
+
uiError(`${f.rel}: ${error.message}`);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
console.log(`${chalk.green(String(copied))} copied, ${chalk.yellow(String(skipped))} skipped`);
|
|
272
|
+
if (copyErrors.length > 0) {
|
|
273
|
+
const noun = copyErrors.length === 1 ? 'error' : 'errors';
|
|
274
|
+
throw new Error(`${copyErrors.length} file ${noun} during sync:\n` +
|
|
275
|
+
copyErrors.map(e => ` ${e.file}: ${e.err.message}`).join('\n'));
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
export default function register(program) {
|
|
279
|
+
program
|
|
280
|
+
.command('sync')
|
|
281
|
+
.description('Copy files from a source to a destination')
|
|
282
|
+
.argument('<source-spec>', 'Source name with optional /path/file pattern')
|
|
283
|
+
.argument('<dest>', 'Destination name')
|
|
284
|
+
.option('--force', 'Skip overwrite confirmation')
|
|
285
|
+
.option('--dry-run', 'Preview without copying')
|
|
286
|
+
.action(handleSync);
|
|
287
|
+
}
|
|
288
|
+
//# sourceMappingURL=sync.js.map
|