diff --git a/README.md b/README.md index 248d25e..c73bdac 100644 --- a/README.md +++ b/README.md @@ -121,6 +121,8 @@ ol document get --raw # outputs raw markdown ol document open # opens in browser ol document create --title "Title" --collection --file doc.md --publish ol document update --file updated.md +ol document update --publish # publishes a draft +ol document update --publish --collection # publishes a draft that has no collection ol document delete --confirm ol document move --collection ol document archive diff --git a/skills/outline-cli/SKILL.md b/skills/outline-cli/SKILL.md index dfacf21..b179f05 100644 --- a/skills/outline-cli/SKILL.md +++ b/skills/outline-cli/SKILL.md @@ -60,6 +60,8 @@ ol doc create --title "Title" --parent --text "# Content" # Nest under pa ol doc create --title "Title" --collection --file ./doc.md ol doc update --title "New Title" ol doc update --file ./updated.md +ol doc update --publish # Publish a draft (create makes drafts unless --publish) +ol doc update --publish --collection # Publish a draft that has no collection (web UI drafts) ol doc move --collection # Move to collection root ol doc move --parent # Nest under parent (collection inferred) ol doc archive diff --git a/src/commands/commands.test.ts b/src/commands/commands.test.ts index e9437ce..6c6b1a9 100644 --- a/src/commands/commands.test.ts +++ b/src/commands/commands.test.ts @@ -234,6 +234,87 @@ describe('document commands', () => { mockExit.mockRestore() }) + it('document update --publish publishes without changing title or text', async () => { + const { apiRequest } = await import('../lib/api.js') + ;(apiRequest as ReturnType).mockResolvedValue({ + data: { + id: 'doc-id', + title: 'Draft', + urlId: 'abc123', + collectionId: 'c1', + updatedAt: '2024-01-01T00:00:00Z', + }, + }) + + const { registerDocumentCommand } = await import('./document.js') + const program = createTestProgram(registerDocumentCommand) + + await program.parseAsync(['node', 'ol', 'document', 'update', 'abc123', '--publish']) + + expect(apiRequest).toHaveBeenLastCalledWith('documents.update', { + id: 'doc-id', + publish: true, + }) + expect(lines(log)[0]).toContain('Published: Draft') + }) + + it('document update --publish --collection sends the resolved collectionId', async () => { + const { apiRequest } = await import('../lib/api.js') + const mockApiRequest = apiRequest as ReturnType + mockApiRequest.mockImplementation((endpoint: string) => { + if (endpoint === 'collections.info') { + return Promise.resolve({ data: { id: 'col-id', name: 'Engineering' } }) + } + return Promise.resolve({ data: { id: 'doc-id', title: 'Draft', urlId: 'abc123' } }) + }) + + const { registerDocumentCommand } = await import('./document.js') + const program = createTestProgram(registerDocumentCommand) + + await program.parseAsync([ + 'node', + 'ol', + 'document', + 'update', + 'abc123', + '--publish', + '--collection', + 'colABC1', + ]) + + expect(mockApiRequest).toHaveBeenLastCalledWith('documents.update', { + id: 'doc-id', + publish: true, + collectionId: 'col-id', + }) + }) + + it('document update --collection without --publish errors before any API call', async () => { + const mockExit = mockProcessExit() + const errorSpy = captureConsole('error') + const { apiRequest } = await import('../lib/api.js') + ;(apiRequest as ReturnType).mockClear() + + const { registerDocumentCommand } = await import('./document.js') + const program = createTestProgram(registerDocumentCommand) + + await expect( + program.parseAsync([ + 'node', + 'ol', + 'document', + 'update', + 'abc123', + '--collection', + 'colABC1', + ]), + ).rejects.toThrow('process.exit(1)') + + expect(lines(errorSpy).join(' ')).toContain('--collection requires --publish') + expect(apiRequest).not.toHaveBeenCalled() + mockExit.mockRestore() + }) + it('document move with --parent infers collectionId from parent', async () => { const { apiRequest } = await import('../lib/api.js') const mockApiRequest = apiRequest as ReturnType diff --git a/src/commands/document.ts b/src/commands/document.ts index d3404ee..1fe3147 100644 --- a/src/commands/document.ts +++ b/src/commands/document.ts @@ -194,8 +194,18 @@ export function registerDocumentCommand(program: Command): void { .option('--title ', 'New title') .option('--text <text>', 'New body (markdown)') .option('--file <path>', 'Read markdown from file') + .option('--publish', 'Publish a draft') + .option('--collection <ref>', 'Collection to publish into (drafts without one only)') .option('--json', 'Output JSON') .action(async (id: string, opts) => { + if (opts.collection && !opts.publish) { + console.error( + formatError('MISSING_OPTION', '--collection requires --publish.', [ + 'Use doc move to move a document to another collection', + ]), + ) + process.exit(1) + } const resolvedId = await resolveDocumentId(id) const body: Record<string, unknown> = { id: resolvedId } @@ -208,13 +218,18 @@ export function registerDocumentCommand(program: Command): void { if (rawText) body.text = rawText if (opts.title) body.title = opts.title } + if (opts.publish) body.publish = true + if (opts.collection) { + body.collectionId = await resolveCollectionId(opts.collection) + } const { data } = await apiRequest<Document>('documents.update', body) if (opts.json) { outputItem(data, formatDoc, essentialKeys, { json: true }) } else { - console.log(chalk.green(`Updated: ${data.title}`), chalk.dim(data.urlId)) + const verb = opts.publish ? 'Published' : 'Updated' + console.log(chalk.green(`${verb}: ${data.title}`), chalk.dim(data.urlId)) } }) diff --git a/src/lib/skills/content.ts b/src/lib/skills/content.ts index 7980f31..2c8cb77 100644 --- a/src/lib/skills/content.ts +++ b/src/lib/skills/content.ts @@ -59,6 +59,8 @@ ol doc create --title "Title" --parent <ref> --text "# Content" # Nest under pa ol doc create --title "Title" --collection <id> --file ./doc.md ol doc update <id> --title "New Title" ol doc update <id> --file ./updated.md +ol doc update <id> --publish # Publish a draft (create makes drafts unless --publish) +ol doc update <id> --publish --collection <ref> # Publish a draft that has no collection (web UI drafts) ol doc move <id> --collection <target-id> # Move to collection root ol doc move <id> --parent <ref> # Nest under parent (collection inferred) ol doc archive <id>