@commandable/integration-data 0.0.6 → 0.0.7
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/dist/credentials-index.d.ts.map +1 -1
- package/dist/credentials-index.js +119 -0
- package/dist/credentials-index.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/loader.d.ts +33 -0
- package/dist/loader.d.ts.map +1 -1
- package/dist/loader.js +36 -4
- package/dist/loader.js.map +1 -1
- package/integrations/__tests__/liveHarness.ts +16 -3
- package/integrations/airtable/.env.test +9 -0
- package/integrations/airtable/.env.test.example +11 -0
- package/integrations/airtable/README.md +27 -0
- package/integrations/airtable/__tests__/get_handlers.test.ts +43 -5
- package/integrations/confluence/.env.test +25 -0
- package/integrations/confluence/.env.test.example +36 -0
- package/integrations/confluence/README.md +28 -0
- package/integrations/confluence/__tests__/get_handlers.test.ts +121 -0
- package/integrations/confluence/__tests__/usage_parity.test.ts +14 -0
- package/integrations/confluence/__tests__/write_handlers.test.ts +131 -0
- package/integrations/confluence/credentials.json +39 -0
- package/integrations/confluence/credentials_hint.md +4 -0
- package/integrations/confluence/credentials_hint_api_token.md +9 -0
- package/integrations/confluence/credentials_hint_oauth_token.md +8 -0
- package/integrations/confluence/handlers/add_comment.js +19 -0
- package/integrations/confluence/handlers/add_label.js +16 -0
- package/integrations/confluence/handlers/create_page.js +22 -0
- package/integrations/confluence/handlers/delete_page.js +17 -0
- package/integrations/confluence/handlers/get_comments.js +33 -0
- package/integrations/confluence/handlers/get_page_children.js +30 -0
- package/integrations/confluence/handlers/get_space.js +22 -0
- package/integrations/confluence/handlers/list_spaces.js +39 -0
- package/integrations/confluence/handlers/read_page.js +49 -0
- package/integrations/confluence/handlers/search_pages.js +42 -0
- package/integrations/confluence/handlers/update_page.js +42 -0
- package/integrations/confluence/manifest.json +85 -0
- package/integrations/confluence/prompt.md +55 -0
- package/integrations/confluence/schemas/add_comment.json +22 -0
- package/integrations/confluence/schemas/add_label.json +19 -0
- package/integrations/confluence/schemas/create_page.json +33 -0
- package/integrations/confluence/schemas/delete_page.json +23 -0
- package/integrations/confluence/schemas/empty.json +6 -0
- package/integrations/confluence/schemas/get_comments.json +24 -0
- package/integrations/confluence/schemas/get_page_children.json +28 -0
- package/integrations/confluence/schemas/get_space.json +18 -0
- package/integrations/confluence/schemas/list_spaces.json +36 -0
- package/integrations/confluence/schemas/read_page.json +28 -0
- package/integrations/confluence/schemas/search_pages.json +26 -0
- package/integrations/confluence/schemas/update_page.json +31 -0
- package/integrations/github/.env.test +16 -0
- package/integrations/github/.env.test.example +17 -0
- package/integrations/github/README.md +75 -0
- package/integrations/github/__tests__/get_handlers.test.ts +5 -5
- package/integrations/github/__tests__/write_handlers.test.ts +176 -58
- package/integrations/github/handlers/create_file.js +46 -0
- package/integrations/github/handlers/delete_file.js +14 -1
- package/integrations/github/handlers/edit_file.js +52 -0
- package/integrations/github/handlers/edit_files.js +107 -0
- package/integrations/github/manifest.json +74 -47
- package/integrations/github/prompt.md +36 -0
- package/integrations/github/schemas/create_file.json +13 -0
- package/integrations/github/schemas/delete_file.json +2 -2
- package/integrations/github/schemas/edit_file.json +26 -0
- package/integrations/github/schemas/edit_files.json +39 -0
- package/integrations/google-calendar/.env.test.example +11 -0
- package/integrations/google-calendar/README.md +41 -0
- package/integrations/google-calendar/__tests__/write_and_admin_handlers.test.ts +7 -7
- package/integrations/google-calendar/manifest.json +27 -17
- package/integrations/google-docs/README.md +30 -0
- package/integrations/google-drive/README.md +26 -0
- package/integrations/google-gmail/.env.test.example +11 -0
- package/integrations/google-gmail/README.md +49 -0
- package/integrations/google-gmail/handlers/create_draft_email.js +1 -1
- package/integrations/google-gmail/handlers/read_email.js +1 -1
- package/integrations/google-gmail/handlers/send_email.js +1 -1
- package/integrations/google-gmail/manifest.json +36 -25
- package/integrations/google-sheet/README.md +27 -0
- package/integrations/google-slides/README.md +28 -0
- package/integrations/hubspot/.env.test.example +20 -0
- package/integrations/hubspot/README.md +48 -0
- package/integrations/hubspot/__tests__/get_handlers.test.ts +151 -0
- package/integrations/hubspot/__tests__/usage_parity.test.ts +10 -0
- package/integrations/hubspot/__tests__/write_handlers.test.ts +244 -0
- package/integrations/hubspot/credentials.json +48 -0
- package/integrations/hubspot/credentials_hint.md +20 -0
- package/integrations/hubspot/credentials_hint_oauth_token.md +16 -0
- package/integrations/hubspot/handlers/archive_company.js +13 -0
- package/integrations/hubspot/handlers/archive_contact.js +13 -0
- package/integrations/hubspot/handlers/archive_deal.js +13 -0
- package/integrations/hubspot/handlers/archive_ticket.js +13 -0
- package/integrations/hubspot/handlers/create_association.js +18 -0
- package/integrations/hubspot/handlers/create_company.js +13 -0
- package/integrations/hubspot/handlers/create_contact.js +14 -0
- package/integrations/hubspot/handlers/create_deal.js +16 -0
- package/integrations/hubspot/handlers/create_note.js +44 -0
- package/integrations/hubspot/handlers/create_task.js +48 -0
- package/integrations/hubspot/handlers/create_ticket.js +15 -0
- package/integrations/hubspot/handlers/get_associations.js +14 -0
- package/integrations/hubspot/handlers/get_company.js +18 -0
- package/integrations/hubspot/handlers/get_contact.js +18 -0
- package/integrations/hubspot/handlers/get_deal.js +18 -0
- package/integrations/hubspot/handlers/get_ticket.js +20 -0
- package/integrations/hubspot/handlers/list_owners.js +12 -0
- package/integrations/hubspot/handlers/list_pipelines.js +5 -0
- package/integrations/hubspot/handlers/list_properties.js +11 -0
- package/integrations/hubspot/handlers/remove_association.js +22 -0
- package/integrations/hubspot/handlers/search_companies.js +43 -0
- package/integrations/hubspot/handlers/search_contacts.js +43 -0
- package/integrations/hubspot/handlers/search_deals.js +43 -0
- package/integrations/hubspot/handlers/search_notes.js +43 -0
- package/integrations/hubspot/handlers/search_tasks.js +43 -0
- package/integrations/hubspot/handlers/search_tickets.js +43 -0
- package/integrations/hubspot/handlers/update_company.js +13 -0
- package/integrations/hubspot/handlers/update_contact.js +14 -0
- package/integrations/hubspot/handlers/update_deal.js +16 -0
- package/integrations/hubspot/handlers/update_task.js +17 -0
- package/integrations/hubspot/handlers/update_ticket.js +15 -0
- package/integrations/hubspot/manifest.json +230 -0
- package/integrations/hubspot/prompt.md +69 -0
- package/integrations/hubspot/schemas/archive_company.json +13 -0
- package/integrations/hubspot/schemas/archive_contact.json +13 -0
- package/integrations/hubspot/schemas/archive_deal.json +9 -0
- package/integrations/hubspot/schemas/archive_ticket.json +9 -0
- package/integrations/hubspot/schemas/create_association.json +24 -0
- package/integrations/hubspot/schemas/create_company.json +14 -0
- package/integrations/hubspot/schemas/create_contact.json +15 -0
- package/integrations/hubspot/schemas/create_deal.json +20 -0
- package/integrations/hubspot/schemas/create_note.json +37 -0
- package/integrations/hubspot/schemas/create_task.json +51 -0
- package/integrations/hubspot/schemas/create_ticket.json +16 -0
- package/integrations/hubspot/schemas/empty.json +6 -0
- package/integrations/hubspot/schemas/get_associations.json +30 -0
- package/integrations/hubspot/schemas/get_company.json +27 -0
- package/integrations/hubspot/schemas/get_contact.json +27 -0
- package/integrations/hubspot/schemas/get_deal.json +20 -0
- package/integrations/hubspot/schemas/get_ticket.json +20 -0
- package/integrations/hubspot/schemas/list_owners.json +25 -0
- package/integrations/hubspot/schemas/list_pipelines.json +13 -0
- package/integrations/hubspot/schemas/list_properties.json +17 -0
- package/integrations/hubspot/schemas/remove_association.json +24 -0
- package/integrations/hubspot/schemas/search_companies.json +56 -0
- package/integrations/hubspot/schemas/search_contacts.json +56 -0
- package/integrations/hubspot/schemas/search_deals.json +43 -0
- package/integrations/hubspot/schemas/search_notes.json +43 -0
- package/integrations/hubspot/schemas/search_tasks.json +43 -0
- package/integrations/hubspot/schemas/search_tickets.json +43 -0
- package/integrations/hubspot/schemas/update_company.json +20 -0
- package/integrations/hubspot/schemas/update_contact.json +21 -0
- package/integrations/hubspot/schemas/update_deal.json +19 -0
- package/integrations/hubspot/schemas/update_task.json +31 -0
- package/integrations/hubspot/schemas/update_ticket.json +18 -0
- package/integrations/jira/.env.test +46 -0
- package/integrations/jira/.env.test.example +41 -0
- package/integrations/jira/README.md +46 -0
- package/integrations/jira/__tests__/get_handlers.test.ts +193 -0
- package/integrations/jira/__tests__/usage_parity.test.ts +14 -0
- package/integrations/jira/__tests__/write_handlers.test.ts +157 -0
- package/integrations/jira/credentials.json +39 -0
- package/integrations/jira/credentials_hint.md +4 -0
- package/integrations/jira/credentials_hint_api_token.md +6 -0
- package/integrations/jira/credentials_hint_oauth_token.md +6 -0
- package/integrations/jira/handlers/add_comment.js +9 -0
- package/integrations/jira/handlers/assign_issue.js +11 -0
- package/integrations/jira/handlers/create_issue.js +37 -0
- package/integrations/jira/handlers/create_sprint.js +19 -0
- package/integrations/jira/handlers/delete_issue.js +10 -0
- package/integrations/jira/handlers/get_backlog_issues.js +13 -0
- package/integrations/jira/handlers/get_board.js +6 -0
- package/integrations/jira/handlers/get_issue.js +63 -0
- package/integrations/jira/handlers/get_issue_comments.js +31 -0
- package/integrations/jira/handlers/get_myself.js +14 -0
- package/integrations/jira/handlers/get_project.js +28 -0
- package/integrations/jira/handlers/get_sprint.js +5 -0
- package/integrations/jira/handlers/get_sprint_issues.js +13 -0
- package/integrations/jira/handlers/get_transitions.js +23 -0
- package/integrations/jira/handlers/list_boards.js +34 -0
- package/integrations/jira/handlers/list_projects.js +29 -0
- package/integrations/jira/handlers/list_sprints.js +29 -0
- package/integrations/jira/handlers/move_issues_to_sprint.js +11 -0
- package/integrations/jira/handlers/search_issues.js +43 -0
- package/integrations/jira/handlers/search_users.js +21 -0
- package/integrations/jira/handlers/transition_issue.js +44 -0
- package/integrations/jira/handlers/update_issue.js +40 -0
- package/integrations/jira/handlers/update_sprint.js +20 -0
- package/integrations/jira/manifest.json +204 -0
- package/integrations/jira/prompt.md +80 -0
- package/integrations/jira/schemas/add_comment.json +16 -0
- package/integrations/jira/schemas/assign_issue.json +16 -0
- package/integrations/jira/schemas/create_issue.json +49 -0
- package/integrations/jira/schemas/create_sprint.json +29 -0
- package/integrations/jira/schemas/delete_issue.json +12 -0
- package/integrations/jira/schemas/empty.json +6 -0
- package/integrations/jira/schemas/get_backlog_issues.json +33 -0
- package/integrations/jira/schemas/get_board.json +13 -0
- package/integrations/jira/schemas/get_issue.json +23 -0
- package/integrations/jira/schemas/get_issue_comments.json +23 -0
- package/integrations/jira/schemas/get_project.json +17 -0
- package/integrations/jira/schemas/get_sprint.json +13 -0
- package/integrations/jira/schemas/get_sprint_issues.json +33 -0
- package/integrations/jira/schemas/get_transitions.json +12 -0
- package/integrations/jira/schemas/list_boards.json +27 -0
- package/integrations/jira/schemas/list_projects.json +22 -0
- package/integrations/jira/schemas/list_sprints.json +29 -0
- package/integrations/jira/schemas/move_issues_to_sprint.json +19 -0
- package/integrations/jira/schemas/search_issues.json +28 -0
- package/integrations/jira/schemas/search_users.json +18 -0
- package/integrations/jira/schemas/transition_issue.json +38 -0
- package/integrations/jira/schemas/update_issue.json +47 -0
- package/integrations/jira/schemas/update_sprint.json +33 -0
- package/integrations/new_integration_prompt.md +173 -2
- package/integrations/notion/.env.test +10 -0
- package/integrations/notion/.env.test.example +13 -0
- package/integrations/notion/README.md +42 -0
- package/integrations/notion/manifest.json +64 -35
- package/integrations/trello/.env.test +6 -0
- package/integrations/trello/.env.test.example +9 -0
- package/integrations/trello/README.md +50 -0
- package/package.json +7 -3
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Confluence (Confluence Cloud) live integration tests
|
|
2
|
+
#
|
|
3
|
+
# Two credential variants are supported — provide one to test that variant:
|
|
4
|
+
#
|
|
5
|
+
# ----------------------
|
|
6
|
+
# Variant: api_token
|
|
7
|
+
# ----------------------
|
|
8
|
+
# CONFLUENCE_DOMAIN:
|
|
9
|
+
# - If your site URL is https://YOUR_DOMAIN.atlassian.net/wiki, then YOUR_DOMAIN is the value.
|
|
10
|
+
CONFLUENCE_DOMAIN=
|
|
11
|
+
# CONFLUENCE_EMAIL:
|
|
12
|
+
# - The email address of the Atlassian account that owns the API token.
|
|
13
|
+
CONFLUENCE_EMAIL=
|
|
14
|
+
# CONFLUENCE_API_TOKEN:
|
|
15
|
+
# - Create an Atlassian API token at: https://id.atlassian.com/manage-profile/security/api-tokens
|
|
16
|
+
CONFLUENCE_API_TOKEN=
|
|
17
|
+
#
|
|
18
|
+
# ----------------------
|
|
19
|
+
# Variant: oauth_token
|
|
20
|
+
# ----------------------
|
|
21
|
+
# CONFLUENCE_CLOUD_ID:
|
|
22
|
+
# - Your Atlassian Cloud resource ID. Discover it via:
|
|
23
|
+
# GET https://api.atlassian.com/oauth/token/accessible-resources (using your access token)
|
|
24
|
+
CONFLUENCE_CLOUD_ID=
|
|
25
|
+
# CONFLUENCE_TOKEN:
|
|
26
|
+
# - OAuth 2.0 access token with Confluence scopes
|
|
27
|
+
# (read:page:confluence, write:page:confluence, read:space:confluence, etc.)
|
|
28
|
+
CONFLUENCE_TOKEN=
|
|
29
|
+
#
|
|
30
|
+
# ----------------------
|
|
31
|
+
# Write test configuration
|
|
32
|
+
# ----------------------
|
|
33
|
+
# Space key to use for write tests (create/update/delete pages)
|
|
34
|
+
CONFLUENCE_TEST_SPACE_KEY=
|
|
35
|
+
# Optional: parent page ID under which test pages will be created
|
|
36
|
+
CONFLUENCE_TEST_PARENT_PAGE_ID=
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Confluence
|
|
2
|
+
|
|
3
|
+
**11 tools**
|
|
4
|
+
|
|
5
|
+

|
|
6
|
+
|
|
7
|
+
## Credential variants
|
|
8
|
+
|
|
9
|
+
| Variant | Label |
|
|
10
|
+
|---|---|
|
|
11
|
+
| `api_token` | API Token (Email + Token) _(default)_ |
|
|
12
|
+
| `oauth_token` | OAuth Access Token (3LO) |
|
|
13
|
+
|
|
14
|
+
## Tools
|
|
15
|
+
|
|
16
|
+
| Tool | Scope | Description |
|
|
17
|
+
|---|---|---|
|
|
18
|
+
| `list_spaces` | read | List Confluence spaces you can access. Use this to discover `spaceId`/`spaceKey` before s… |
|
|
19
|
+
| `get_space` | read | Get a Confluence space by `spaceId`, including its homepage and basic metadata. |
|
|
20
|
+
| `search_pages` | read | Search for pages using CQL (Confluence Query Language). Use this as the primary discovery… |
|
|
21
|
+
| `read_page` | read | Read a page and return agent-friendly Markdown extracted from Confluence storage format (… |
|
|
22
|
+
| `get_page_children` | read | List child content under a page (typically child pages). Use this to navigate page hierar… |
|
|
23
|
+
| `get_comments` | read | List footer comments for a page, optionally including their child replies. Use this to re… |
|
|
24
|
+
| `create_page` | write | Create a new Confluence page. Provide the body as Confluence storage format (XHTML). For … |
|
|
25
|
+
| `update_page` | write | Update an existing Confluence page. This tool auto-fetches the current version number and… |
|
|
26
|
+
| `delete_page` | write | Delete a Confluence page (moves it to trash by default). Use carefully. |
|
|
27
|
+
| `add_comment` | write | Add a footer comment to a Confluence page. Provide the body as Confluence storage format … |
|
|
28
|
+
| `add_label` | write | Add one or more labels to a Confluence page. Labels are useful for discovery via CQL (`la… |
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { beforeAll, describe, expect, it } from 'vitest'
|
|
2
|
+
import { createCredentialStore, createIntegrationNode, createProxy, createToolbox, hasEnv } from '../../__tests__/liveHarness.js'
|
|
3
|
+
|
|
4
|
+
// LIVE Confluence read tests using credentials
|
|
5
|
+
//
|
|
6
|
+
// Variant: api_token
|
|
7
|
+
// - CONFLUENCE_DOMAIN
|
|
8
|
+
// - CONFLUENCE_EMAIL
|
|
9
|
+
// - CONFLUENCE_API_TOKEN
|
|
10
|
+
|
|
11
|
+
const env = process.env as Record<string, string | undefined>
|
|
12
|
+
|
|
13
|
+
const suiteOrSkip = hasEnv('CONFLUENCE_DOMAIN', 'CONFLUENCE_EMAIL', 'CONFLUENCE_API_TOKEN') ? describe : describe.skip
|
|
14
|
+
|
|
15
|
+
suiteOrSkip('confluence read handlers (live)', () => {
|
|
16
|
+
describe('variant: api_token', () => {
|
|
17
|
+
const ctx: {
|
|
18
|
+
spaceId?: string
|
|
19
|
+
spaceKey?: string
|
|
20
|
+
homepageId?: string
|
|
21
|
+
pageId?: string
|
|
22
|
+
} = {}
|
|
23
|
+
|
|
24
|
+
let confluence: ReturnType<typeof createToolbox>
|
|
25
|
+
|
|
26
|
+
beforeAll(async () => {
|
|
27
|
+
const credentialStore = createCredentialStore(async () => ({
|
|
28
|
+
domain: env.CONFLUENCE_DOMAIN!,
|
|
29
|
+
email: env.CONFLUENCE_EMAIL!,
|
|
30
|
+
apiToken: env.CONFLUENCE_API_TOKEN!,
|
|
31
|
+
}))
|
|
32
|
+
const proxy = createProxy(credentialStore)
|
|
33
|
+
confluence = createToolbox(
|
|
34
|
+
'confluence',
|
|
35
|
+
proxy,
|
|
36
|
+
createIntegrationNode('confluence', { label: 'Confluence', credentialId: 'confluence-creds', credentialVariant: 'api_token' }),
|
|
37
|
+
'api_token',
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
const list_spaces = confluence.read('list_spaces')
|
|
42
|
+
const spaces = await list_spaces({ limit: 10 })
|
|
43
|
+
const first = spaces?.results?.[0]
|
|
44
|
+
ctx.spaceId = first?.id
|
|
45
|
+
ctx.spaceKey = first?.key
|
|
46
|
+
ctx.homepageId = first?.homepageId
|
|
47
|
+
}
|
|
48
|
+
catch {}
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
if (ctx.spaceKey) {
|
|
52
|
+
const search_pages = confluence.read('search_pages')
|
|
53
|
+
const found = await search_pages({
|
|
54
|
+
cql: `space = "${ctx.spaceKey}" AND type = page ORDER BY lastmodified DESC`,
|
|
55
|
+
limit: 5,
|
|
56
|
+
})
|
|
57
|
+
const first = found?.results?.[0]
|
|
58
|
+
ctx.pageId = first?.id || ctx.homepageId
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
ctx.pageId = ctx.homepageId
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
ctx.pageId = ctx.pageId || ctx.homepageId
|
|
66
|
+
}
|
|
67
|
+
}, 60000)
|
|
68
|
+
|
|
69
|
+
it('list_spaces returns spaces', async () => {
|
|
70
|
+
const list_spaces = confluence.read('list_spaces')
|
|
71
|
+
const res = await list_spaces({ limit: 10 })
|
|
72
|
+
expect(Array.isArray(res?.results)).toBe(true)
|
|
73
|
+
}, 30000)
|
|
74
|
+
|
|
75
|
+
it('get_space returns a space when spaceId is available', async () => {
|
|
76
|
+
if (!ctx.spaceId)
|
|
77
|
+
return expect(true).toBe(true)
|
|
78
|
+
const get_space = confluence.read('get_space')
|
|
79
|
+
const res = await get_space({ spaceId: ctx.spaceId })
|
|
80
|
+
expect(res?.id).toBe(ctx.spaceId)
|
|
81
|
+
}, 30000)
|
|
82
|
+
|
|
83
|
+
it('search_pages returns results for a basic CQL query (best effort)', async () => {
|
|
84
|
+
if (!ctx.spaceKey)
|
|
85
|
+
return expect(true).toBe(true)
|
|
86
|
+
const search_pages = confluence.read('search_pages')
|
|
87
|
+
const res = await search_pages({
|
|
88
|
+
cql: `space = "${ctx.spaceKey}" AND type = page ORDER BY lastmodified DESC`,
|
|
89
|
+
limit: 5,
|
|
90
|
+
})
|
|
91
|
+
expect(Array.isArray(res?.results)).toBe(true)
|
|
92
|
+
}, 30000)
|
|
93
|
+
|
|
94
|
+
it('read_page returns page content (storage) when pageId is available', async () => {
|
|
95
|
+
if (!ctx.pageId)
|
|
96
|
+
return expect(true).toBe(true)
|
|
97
|
+
const read_page = confluence.read('read_page')
|
|
98
|
+
const page = await read_page({ pageId: ctx.pageId, outputMarkdown: false })
|
|
99
|
+
expect(page?.id).toBe(ctx.pageId)
|
|
100
|
+
// Can be null for unusual pages; just require a usable response.
|
|
101
|
+
expect(page).toBeTruthy()
|
|
102
|
+
}, 30000)
|
|
103
|
+
|
|
104
|
+
it('get_page_children returns an array when pageId is available (best effort)', async () => {
|
|
105
|
+
if (!ctx.pageId)
|
|
106
|
+
return expect(true).toBe(true)
|
|
107
|
+
const get_page_children = confluence.read('get_page_children')
|
|
108
|
+
const res = await get_page_children({ pageId: ctx.pageId, limit: 10 })
|
|
109
|
+
expect(Array.isArray(res?.results)).toBe(true)
|
|
110
|
+
}, 30000)
|
|
111
|
+
|
|
112
|
+
it('get_comments returns an array when pageId is available (best effort)', async () => {
|
|
113
|
+
if (!ctx.pageId)
|
|
114
|
+
return expect(true).toBe(true)
|
|
115
|
+
const get_comments = confluence.read('get_comments')
|
|
116
|
+
const res = await get_comments({ pageId: ctx.pageId, limit: 10 })
|
|
117
|
+
expect(Array.isArray(res?.results)).toBe(true)
|
|
118
|
+
}, 30000)
|
|
119
|
+
})
|
|
120
|
+
})
|
|
121
|
+
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import { getMissingToolUsages } from '../../__tests__/usageParity.js'
|
|
3
|
+
|
|
4
|
+
describe('confluence static usage parity', () => {
|
|
5
|
+
it('every api_token tool is referenced in tests', () => {
|
|
6
|
+
const missing = getMissingToolUsages({
|
|
7
|
+
integrationName: 'confluence',
|
|
8
|
+
importMetaUrl: import.meta.url,
|
|
9
|
+
credentialVariant: 'api_token',
|
|
10
|
+
})
|
|
11
|
+
expect(missing, `Missing handler usages in tests: ${missing.join(', ')}`).toEqual([])
|
|
12
|
+
})
|
|
13
|
+
})
|
|
14
|
+
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { afterAll, beforeAll, describe, expect, it } from 'vitest'
|
|
2
|
+
import { createCredentialStore, createIntegrationNode, createProxy, createToolbox, hasEnv, safeCleanup } from '../../__tests__/liveHarness.js'
|
|
3
|
+
|
|
4
|
+
// LIVE Confluence write tests using credentials
|
|
5
|
+
//
|
|
6
|
+
// Required for write tests:
|
|
7
|
+
// - CONFLUENCE_TEST_SPACE_KEY
|
|
8
|
+
// Optional:
|
|
9
|
+
// - CONFLUENCE_TEST_PARENT_PAGE_ID
|
|
10
|
+
//
|
|
11
|
+
// Variant: api_token
|
|
12
|
+
// - CONFLUENCE_DOMAIN
|
|
13
|
+
// - CONFLUENCE_EMAIL
|
|
14
|
+
// - CONFLUENCE_API_TOKEN
|
|
15
|
+
|
|
16
|
+
const env = process.env as Record<string, string | undefined>
|
|
17
|
+
|
|
18
|
+
const suiteOrSkip = (hasEnv('CONFLUENCE_DOMAIN', 'CONFLUENCE_EMAIL', 'CONFLUENCE_API_TOKEN') && hasEnv('CONFLUENCE_TEST_SPACE_KEY'))
|
|
19
|
+
? describe
|
|
20
|
+
: describe.skip
|
|
21
|
+
|
|
22
|
+
suiteOrSkip('confluence write handlers (live)', () => {
|
|
23
|
+
describe('variant: api_token', () => {
|
|
24
|
+
const ctx: {
|
|
25
|
+
spaceId?: string
|
|
26
|
+
createdPageId?: string
|
|
27
|
+
createdLabel?: string
|
|
28
|
+
createdCommentId?: string
|
|
29
|
+
} = {}
|
|
30
|
+
|
|
31
|
+
let confluence: ReturnType<typeof createToolbox>
|
|
32
|
+
|
|
33
|
+
beforeAll(async () => {
|
|
34
|
+
const credentialStore = createCredentialStore(async () => ({
|
|
35
|
+
domain: env.CONFLUENCE_DOMAIN!,
|
|
36
|
+
email: env.CONFLUENCE_EMAIL!,
|
|
37
|
+
apiToken: env.CONFLUENCE_API_TOKEN!,
|
|
38
|
+
}))
|
|
39
|
+
const proxy = createProxy(credentialStore)
|
|
40
|
+
confluence = createToolbox(
|
|
41
|
+
'confluence',
|
|
42
|
+
proxy,
|
|
43
|
+
createIntegrationNode('confluence', { label: 'Confluence', credentialId: 'confluence-creds', credentialVariant: 'api_token' }),
|
|
44
|
+
'api_token',
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
// Resolve spaceId from configured space key.
|
|
48
|
+
try {
|
|
49
|
+
const list_spaces = confluence.read('list_spaces')
|
|
50
|
+
const spaces = await list_spaces({ keys: [env.CONFLUENCE_TEST_SPACE_KEY!], limit: 5 })
|
|
51
|
+
const first = spaces?.results?.[0]
|
|
52
|
+
ctx.spaceId = first?.id
|
|
53
|
+
}
|
|
54
|
+
catch {}
|
|
55
|
+
}, 60000)
|
|
56
|
+
|
|
57
|
+
afterAll(async () => {
|
|
58
|
+
await safeCleanup(async () => {
|
|
59
|
+
if (!ctx.createdPageId)
|
|
60
|
+
return
|
|
61
|
+
const delete_page = confluence.write('delete_page')
|
|
62
|
+
await delete_page({ pageId: ctx.createdPageId })
|
|
63
|
+
})
|
|
64
|
+
}, 60000)
|
|
65
|
+
|
|
66
|
+
it('create_page -> read_page -> update_page -> add_label -> add_comment -> get_comments -> delete_page roundtrip', async () => {
|
|
67
|
+
if (!ctx.spaceId)
|
|
68
|
+
return expect(true).toBe(true)
|
|
69
|
+
|
|
70
|
+
const parentId = env.CONFLUENCE_TEST_PARENT_PAGE_ID || null
|
|
71
|
+
const timestamp = Date.now()
|
|
72
|
+
|
|
73
|
+
const create_page = confluence.write('create_page')
|
|
74
|
+
const created = await create_page({
|
|
75
|
+
spaceId: ctx.spaceId,
|
|
76
|
+
parentId,
|
|
77
|
+
title: `CmdTest Confluence ${timestamp}`,
|
|
78
|
+
bodyStorage: `<p>Created by integration tests at ${timestamp}.</p>`,
|
|
79
|
+
})
|
|
80
|
+
const pageId = created?.id
|
|
81
|
+
expect(pageId).toBeTruthy()
|
|
82
|
+
ctx.createdPageId = pageId
|
|
83
|
+
|
|
84
|
+
const read_page = confluence.read('read_page')
|
|
85
|
+
const got = await read_page({ pageId, includeLabels: true, outputMarkdown: true })
|
|
86
|
+
expect(got?.id).toBe(pageId)
|
|
87
|
+
|
|
88
|
+
const update_page = confluence.write('update_page')
|
|
89
|
+
const updated = await update_page({
|
|
90
|
+
pageId,
|
|
91
|
+
title: `CmdTest Confluence Updated ${timestamp}`,
|
|
92
|
+
bodyStorage: `<p>Updated by integration tests at ${timestamp}.</p>`,
|
|
93
|
+
versionMessage: 'Updated by integration tests',
|
|
94
|
+
})
|
|
95
|
+
expect(updated?.id || updated?.title || updated).toBeTruthy()
|
|
96
|
+
|
|
97
|
+
const add_label = confluence.write('add_label')
|
|
98
|
+
const label = `cmdtest-${timestamp}`
|
|
99
|
+
ctx.createdLabel = label
|
|
100
|
+
const labelRes = await add_label({ pageId, labels: [label] })
|
|
101
|
+
expect(labelRes).toBeTruthy()
|
|
102
|
+
|
|
103
|
+
// Verify label via read_page (labels are included when includeLabels is true)
|
|
104
|
+
const afterLabel = await read_page({ pageId, includeLabels: true, outputMarkdown: false })
|
|
105
|
+
const labelNames = Array.isArray(afterLabel?.labels)
|
|
106
|
+
? afterLabel.labels.map((l: any) => l?.name).filter(Boolean)
|
|
107
|
+
: []
|
|
108
|
+
expect(labelNames.includes(label)).toBe(true)
|
|
109
|
+
|
|
110
|
+
const add_comment = confluence.write('add_comment')
|
|
111
|
+
const commentText = `Test comment ${timestamp}`
|
|
112
|
+
const comment = await add_comment({ pageId, bodyStorage: `<p>${commentText}</p>` })
|
|
113
|
+
ctx.createdCommentId = comment?.id
|
|
114
|
+
expect(comment?.id || comment).toBeTruthy()
|
|
115
|
+
|
|
116
|
+
const get_comments = confluence.read('get_comments')
|
|
117
|
+
const comments = await get_comments({ pageId, limit: 50 })
|
|
118
|
+
const found = (comments?.results || []).some((c: any) =>
|
|
119
|
+
(c?.id && ctx.createdCommentId && String(c.id) === String(ctx.createdCommentId))
|
|
120
|
+
|| (c?.body && JSON.stringify(c.body).includes(commentText)),
|
|
121
|
+
)
|
|
122
|
+
expect(found).toBe(true)
|
|
123
|
+
|
|
124
|
+
const delete_page = confluence.write('delete_page')
|
|
125
|
+
const deleted = await delete_page({ pageId })
|
|
126
|
+
expect(deleted?.ok === true || deleted).toBeTruthy()
|
|
127
|
+
ctx.createdPageId = undefined
|
|
128
|
+
}, 150000)
|
|
129
|
+
})
|
|
130
|
+
})
|
|
131
|
+
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"variants": {
|
|
3
|
+
"api_token": {
|
|
4
|
+
"label": "API Token (Email + Token)",
|
|
5
|
+
"schema": {
|
|
6
|
+
"type": "object",
|
|
7
|
+
"properties": {
|
|
8
|
+
"domain": {
|
|
9
|
+
"type": "string",
|
|
10
|
+
"title": "Confluence site domain",
|
|
11
|
+
"description": "The subdomain of your Confluence Cloud site. Example: for https://mycompany.atlassian.net/wiki, enter 'mycompany'."
|
|
12
|
+
},
|
|
13
|
+
"email": {
|
|
14
|
+
"type": "string",
|
|
15
|
+
"title": "Atlassian account email",
|
|
16
|
+
"description": "Email address of the Atlassian account that owns the API token."
|
|
17
|
+
},
|
|
18
|
+
"apiToken": {
|
|
19
|
+
"type": "string",
|
|
20
|
+
"title": "Atlassian API token",
|
|
21
|
+
"description": "Atlassian API token for Confluence Cloud. The server will convert this into the required Basic auth header."
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"required": ["domain", "email", "apiToken"],
|
|
25
|
+
"additionalProperties": false
|
|
26
|
+
},
|
|
27
|
+
"baseUrlTemplate": "https://{{domain}}.atlassian.net",
|
|
28
|
+
"injection": {
|
|
29
|
+
"headers": {
|
|
30
|
+
"Authorization": "Basic {{basicAuth}}",
|
|
31
|
+
"Accept": "application/json"
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"preprocess": "confluence_api_token"
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
"default": "api_token"
|
|
38
|
+
}
|
|
39
|
+
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
Set up Confluence Cloud API token auth:
|
|
2
|
+
|
|
3
|
+
1. Open your Confluence site (it looks like `https://YOUR_DOMAIN.atlassian.net/wiki`)
|
|
4
|
+
2. Copy `YOUR_DOMAIN` (the subdomain) and use it as `domain`
|
|
5
|
+
3. Create an Atlassian API token at `https://id.atlassian.com/manage-profile/security/api-tokens`
|
|
6
|
+
4. Use the Atlassian account email as `email` and paste the token as `apiToken`
|
|
7
|
+
|
|
8
|
+
Note: The server computes the required Basic auth header for you.
|
|
9
|
+
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
Set up Confluence Cloud OAuth (3LO):
|
|
2
|
+
|
|
3
|
+
1. Create an OAuth 2.0 (3LO) app in the Atlassian developer console
|
|
4
|
+
2. Add Confluence scopes (typical minimum): read:page:confluence, write:page:confluence, read:space:confluence (plus offline_access if you want refresh tokens)
|
|
5
|
+
3. Complete the OAuth flow to obtain an access token
|
|
6
|
+
4. Call `GET https://api.atlassian.com/oauth/token/accessible-resources` with `Authorization: Bearer <access_token>` to get `cloudId`
|
|
7
|
+
5. Paste `cloudId` and the OAuth access `token` here
|
|
8
|
+
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
async (input) => {
|
|
2
|
+
const body = {
|
|
3
|
+
pageId: String(input.pageId),
|
|
4
|
+
body: {
|
|
5
|
+
representation: 'storage',
|
|
6
|
+
value: String(input.bodyStorage),
|
|
7
|
+
},
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
if (input.parentCommentId)
|
|
11
|
+
body.parentCommentId = String(input.parentCommentId)
|
|
12
|
+
|
|
13
|
+
const res = await integration.fetch('/wiki/api/v2/footer-comments', {
|
|
14
|
+
method: 'POST',
|
|
15
|
+
body,
|
|
16
|
+
})
|
|
17
|
+
return await res.json()
|
|
18
|
+
}
|
|
19
|
+
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
async (input) => {
|
|
2
|
+
const pageId = encodeURIComponent(String(input.pageId))
|
|
3
|
+
const labels = Array.isArray(input.labels) ? input.labels : []
|
|
4
|
+
|
|
5
|
+
const body = labels
|
|
6
|
+
.map((name) => String(name).trim())
|
|
7
|
+
.filter(Boolean)
|
|
8
|
+
.map((name) => ({ prefix: 'global', name }))
|
|
9
|
+
|
|
10
|
+
const res = await integration.fetch(`/wiki/rest/api/content/${pageId}/label`, {
|
|
11
|
+
method: 'POST',
|
|
12
|
+
body,
|
|
13
|
+
})
|
|
14
|
+
return await res.json()
|
|
15
|
+
}
|
|
16
|
+
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
async (input) => {
|
|
2
|
+
const body = {
|
|
3
|
+
spaceId: String(input.spaceId),
|
|
4
|
+
status: input.status || 'current',
|
|
5
|
+
title: String(input.title),
|
|
6
|
+
body: {
|
|
7
|
+
representation: 'storage',
|
|
8
|
+
value: String(input.bodyStorage),
|
|
9
|
+
},
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
if (input.parentId)
|
|
13
|
+
body.parentId = String(input.parentId)
|
|
14
|
+
|
|
15
|
+
const res = await integration.fetch('/wiki/api/v2/pages', {
|
|
16
|
+
method: 'POST',
|
|
17
|
+
body,
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
return await res.json()
|
|
21
|
+
}
|
|
22
|
+
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
async (input) => {
|
|
2
|
+
const pageId = encodeURIComponent(String(input.pageId))
|
|
3
|
+
const params = new URLSearchParams()
|
|
4
|
+
if (input.purge) params.set('purge', 'true')
|
|
5
|
+
if (input.draft) params.set('draft', 'true')
|
|
6
|
+
|
|
7
|
+
const res = await integration.fetch(`/wiki/api/v2/pages/${pageId}${params.toString() ? `?${params}` : ''}`, {
|
|
8
|
+
method: 'DELETE',
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
if (res.status === 204)
|
|
12
|
+
return { ok: true }
|
|
13
|
+
|
|
14
|
+
// Some proxies/APIs may still return JSON; try to parse.
|
|
15
|
+
try { return await res.json() } catch { return { ok: res.ok, status: res.status } }
|
|
16
|
+
}
|
|
17
|
+
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
async (input) => {
|
|
2
|
+
const pageId = encodeURIComponent(String(input.pageId))
|
|
3
|
+
const params = new URLSearchParams()
|
|
4
|
+
|
|
5
|
+
params.set('body-format', 'STORAGE')
|
|
6
|
+
|
|
7
|
+
const limit = typeof input.limit === 'number' ? input.limit : undefined
|
|
8
|
+
if (limit) params.set('limit', String(limit))
|
|
9
|
+
if (input.cursor) params.set('cursor', String(input.cursor))
|
|
10
|
+
|
|
11
|
+
const res = await integration.fetch(`/wiki/api/v2/pages/${pageId}/footer-comments?${params}`)
|
|
12
|
+
const data = await res.json()
|
|
13
|
+
|
|
14
|
+
const results = Array.isArray(data?.results)
|
|
15
|
+
? data.results.map((c) => ({
|
|
16
|
+
id: c.id,
|
|
17
|
+
status: c.status,
|
|
18
|
+
title: c.title,
|
|
19
|
+
pageId: c.pageId,
|
|
20
|
+
version: c?.version?.number,
|
|
21
|
+
authorId: c?.version?.authorId,
|
|
22
|
+
createdAt: c?.version?.createdAt,
|
|
23
|
+
body: c?.body,
|
|
24
|
+
webui: c?._links?.webui,
|
|
25
|
+
}))
|
|
26
|
+
: []
|
|
27
|
+
|
|
28
|
+
return {
|
|
29
|
+
results,
|
|
30
|
+
links: data?._links || {},
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
async (input) => {
|
|
2
|
+
const pageId = encodeURIComponent(String(input.pageId))
|
|
3
|
+
const params = new URLSearchParams()
|
|
4
|
+
|
|
5
|
+
const limit = typeof input.limit === 'number' ? input.limit : undefined
|
|
6
|
+
if (limit) params.set('limit', String(limit))
|
|
7
|
+
if (input.cursor) params.set('cursor', String(input.cursor))
|
|
8
|
+
if (typeof input.sort === 'string' && input.sort) params.set('sort', input.sort)
|
|
9
|
+
|
|
10
|
+
const res = await integration.fetch(`/wiki/api/v2/pages/${pageId}/children${params.toString() ? `?${params}` : ''}`)
|
|
11
|
+
const data = await res.json()
|
|
12
|
+
|
|
13
|
+
const results = Array.isArray(data?.results)
|
|
14
|
+
? data.results.map((c) => ({
|
|
15
|
+
id: c.id,
|
|
16
|
+
type: c.type,
|
|
17
|
+
status: c.status,
|
|
18
|
+
title: c.title,
|
|
19
|
+
parentId: c.parentId,
|
|
20
|
+
spaceId: c.spaceId,
|
|
21
|
+
links: c?._links || {},
|
|
22
|
+
}))
|
|
23
|
+
: []
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
results,
|
|
27
|
+
links: data?._links || {},
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
async (input) => {
|
|
2
|
+
const spaceId = encodeURIComponent(String(input.spaceId))
|
|
3
|
+
const params = new URLSearchParams()
|
|
4
|
+
if (input.includePermissions) params.set('include-permissions', 'true')
|
|
5
|
+
|
|
6
|
+
const path = `/wiki/api/v2/spaces/${spaceId}${params.toString() ? `?${params}` : ''}`
|
|
7
|
+
const res = await integration.fetch(path)
|
|
8
|
+
const data = await res.json()
|
|
9
|
+
|
|
10
|
+
return {
|
|
11
|
+
id: data?.id,
|
|
12
|
+
key: data?.key,
|
|
13
|
+
name: data?.name,
|
|
14
|
+
type: data?.type,
|
|
15
|
+
status: data?.status,
|
|
16
|
+
homepageId: data?.homepageId,
|
|
17
|
+
description: data?.description,
|
|
18
|
+
links: data?._links || {},
|
|
19
|
+
raw: data,
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
async (input) => {
|
|
2
|
+
const params = new URLSearchParams()
|
|
3
|
+
|
|
4
|
+
if (Array.isArray(input.ids))
|
|
5
|
+
for (const id of input.ids) params.append('ids', String(id))
|
|
6
|
+
if (Array.isArray(input.keys))
|
|
7
|
+
for (const key of input.keys) params.append('keys', String(key))
|
|
8
|
+
if (typeof input.type === 'string' && input.type)
|
|
9
|
+
params.set('type', input.type)
|
|
10
|
+
if (typeof input.status === 'string' && input.status)
|
|
11
|
+
params.set('status', input.status)
|
|
12
|
+
|
|
13
|
+
const limit = typeof input.limit === 'number' ? input.limit : undefined
|
|
14
|
+
if (limit) params.set('limit', String(limit))
|
|
15
|
+
|
|
16
|
+
if (input.cursor) params.set('cursor', String(input.cursor))
|
|
17
|
+
|
|
18
|
+
const path = `/wiki/api/v2/spaces${params.toString() ? `?${params}` : ''}`
|
|
19
|
+
const res = await integration.fetch(path)
|
|
20
|
+
const data = await res.json()
|
|
21
|
+
|
|
22
|
+
const results = Array.isArray(data?.results)
|
|
23
|
+
? data.results.map((s) => ({
|
|
24
|
+
id: s.id,
|
|
25
|
+
key: s.key,
|
|
26
|
+
name: s.name,
|
|
27
|
+
type: s.type,
|
|
28
|
+
status: s.status,
|
|
29
|
+
homepageId: s.homepageId,
|
|
30
|
+
webui: s?._links?.webui,
|
|
31
|
+
}))
|
|
32
|
+
: []
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
results,
|
|
36
|
+
links: data?._links || {},
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
async (input) => {
|
|
2
|
+
const buildWebUrl = (links) => {
|
|
3
|
+
const base = links?.base
|
|
4
|
+
const webui = links?.webui
|
|
5
|
+
if (!webui) return null
|
|
6
|
+
if (/^https?:\/\//.test(webui)) return webui
|
|
7
|
+
if (base && /^https?:\/\//.test(base)) {
|
|
8
|
+
try { return new URL(webui, base).toString() } catch {}
|
|
9
|
+
}
|
|
10
|
+
return webui
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const pageId = encodeURIComponent(String(input.pageId))
|
|
14
|
+
|
|
15
|
+
const params = new URLSearchParams()
|
|
16
|
+
params.set('body-format', 'storage')
|
|
17
|
+
if (input.includeLabels) params.set('include-labels', 'true')
|
|
18
|
+
if (input.includeProperties) params.set('include-properties', 'true')
|
|
19
|
+
|
|
20
|
+
const res = await integration.fetch(`/wiki/api/v2/pages/${pageId}?${params}`)
|
|
21
|
+
const data = await res.json()
|
|
22
|
+
|
|
23
|
+
const storage = data?.body?.storage
|
|
24
|
+
const storageValue = typeof storage === 'string'
|
|
25
|
+
? storage
|
|
26
|
+
: (typeof storage?.value === 'string' ? storage.value : '')
|
|
27
|
+
|
|
28
|
+
const meta = {
|
|
29
|
+
id: data?.id,
|
|
30
|
+
title: data?.title,
|
|
31
|
+
spaceId: data?.spaceId,
|
|
32
|
+
parentId: data?.parentId,
|
|
33
|
+
status: data?.status,
|
|
34
|
+
version: data?.version?.number,
|
|
35
|
+
createdAt: data?.createdAt,
|
|
36
|
+
webUrl: buildWebUrl(data?._links),
|
|
37
|
+
labels: data?.labels?.results,
|
|
38
|
+
links: data?._links || {},
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (input.outputMarkdown) {
|
|
42
|
+
const md = storageValue ? (utils.html?.toMarkdown(storageValue) || '') : ''
|
|
43
|
+
const note = '\n\n---\n_System note: this is a Markdown representation of the page. If you intend to edit it, re-fetch without `outputMarkdown` to get the storage XHTML and avoid accidentally deleting macros or hidden content._'
|
|
44
|
+
return { ...meta, contentMarkdown: md ? md + note : null }
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return { ...meta, contentStorage: storageValue || null }
|
|
48
|
+
}
|
|
49
|
+
|