diff options
Diffstat (limited to 'src/cli')
| -rw-r--r-- | src/cli/main.ts | 35 | ||||
| -rw-r--r-- | src/cli/overleafApi.ts | 11 | ||||
| -rw-r--r-- | src/cli/test.ts | 25 |
3 files changed, 71 insertions, 0 deletions
diff --git a/src/cli/main.ts b/src/cli/main.ts index 1fa9fce..52573fe 100644 --- a/src/cli/main.ts +++ b/src/cli/main.ts @@ -186,6 +186,37 @@ async function cmdProjects(flags: Record<string, string | boolean>): Promise<voi } } +async function cmdCopy(positional: string[], flags: Record<string, string | boolean>): Promise<void> { + const sourceRef = positional[0] + const newName = positional[1] + if (!sourceRef || !newName) { + exitWith(EXIT_USAGE, 'Usage: lattex-cli copy <source-id|name> <new-name>') + } + + const cookie = await resolveAuth(flags) + const api = new OverleafApi(cookie) + + // Resolve project ID from name if needed + let sourceId = sourceRef + if (!sourceRef.match(/^[0-9a-f]{24}$/)) { + const projects = await api.listProjects() + const match = projects.find(p => p.name === sourceRef) + if (!match) { + exitWith(EXIT_ERROR, `Project not found: ${sourceRef}`) + } + sourceId = match!.id + } + + err(`Copying project ${sourceId} as "${newName}"...`) + const newId = await api.copyProject(sourceId, newName) + + if (jsonMode) { + jsonOut({ ok: true, sourceId, newId, newName }) + } else { + out(`Copied → ${newId} "${newName}"`) + } +} + async function cmdClone(positional: string[], flags: Record<string, string | boolean>): Promise<void> { const projectRef = positional[0] const targetDir = positional[1] @@ -693,6 +724,7 @@ Commands: projects [--json] List Overleaf projects + copy <id|name> <name> Copy (clone) a project on Overleaf clone <id|name> <dir> Download project to local directory pull <dir> Update local dir from Overleaf status <dir> Show local changes @@ -726,6 +758,9 @@ async function main(): Promise<void> { case 'projects': await cmdProjects(parsed.flags) break + case 'copy': + await cmdCopy(parsed.positional, parsed.flags) + break case 'clone': await cmdClone(parsed.positional, parsed.flags) break diff --git a/src/cli/overleafApi.ts b/src/cli/overleafApi.ts index 6301638..94bdac2 100644 --- a/src/cli/overleafApi.ts +++ b/src/cli/overleafApi.ts @@ -348,6 +348,17 @@ export class OverleafApi { } } + /** Clone (copy) a project on Overleaf, returning the new project ID */ + async copyProject(sourceProjectId: string, newName: string): Promise<string> { + const result = await this.requestWithCsrf('POST', `/project/${sourceProjectId}/clone`, { + projectName: newName + }) + if (!result.ok || !(result.data as any)?.project_id) { + throw new Error(`Copy project failed: HTTP ${result.status}`) + } + return (result.data as any).project_id + } + /** Flush project (ensure OT changes are saved to database) */ async flushProject(projectId: string): Promise<void> { await this.requestWithCsrf('POST', `/project/${projectId}/flush`) diff --git a/src/cli/test.ts b/src/cli/test.ts index e76182e..f81a89b 100644 --- a/src/cli/test.ts +++ b/src/cli/test.ts @@ -1,5 +1,6 @@ // Tests for CLI arg parsing and diff logic import { parseArgs } from './args' +import { OverleafApi } from './overleafApi' import { walkRootFolder } from './fileTree' import { join } from 'path' import { mkdtemp, writeFile, mkdir, rm } from 'fs/promises' @@ -229,6 +230,28 @@ l.3 \\usepackage{nonexistent}` eq(entries4[0].line, 20, 'overfull line') } +// ── copy command arg parsing test ── + +function testCopyArgs(): void { + console.log('--- copy args ---') + + const r1 = parseArgs(['node', 'cli', 'copy', '6a965e135690947a3be20882', 'My New Project']) + eq(r1.command, 'copy', 'copy command') + eq(r1.positional, ['6a965e135690947a3be20882', 'My New Project'], 'copy positionals') + + const r2 = parseArgs(['node', 'cli', 'copy', 'My Source', 'My Copy', '--json']) + eq(r2.command, 'copy', 'copy command with json') + eq(r2.positional, ['My Source', 'My Copy'], 'copy positionals by name') + eq(r2.flags.json, true, 'json flag with copy') +} + +// ── OverleafApi.copyProject existence test ── + +function testCopyProjectMethod(): void { + console.log('--- copyProject method ---') + assert(typeof OverleafApi.prototype.copyProject === 'function', 'copyProject is a method on OverleafApi') +} + // ── Run all tests ── async function runTests(): Promise<void> { @@ -237,6 +260,8 @@ async function runTests(): Promise<void> { testIsTextFile() await testComputeDiff() testParseCompileLog() + testCopyArgs() + testCopyProjectMethod() console.log(`\n${passed} passed, ${failed} failed`) process.exit(failed > 0 ? 1 : 0) |
