Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 17 additions & 22 deletions bin/confluence.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,6 @@ const { getConfig, initConfig, listProfiles, setActiveProfile, deleteProfile, is
const Analytics = require('../lib/analytics');
const pkg = require('../package.json');

function buildPageUrl(config, path) {
const protocol = config.protocol || 'https';
return `${protocol}://${config.domain}${path}`;
}

function assertWritable(config) {
if (config.readOnly) {
console.error(chalk.red('Error: This profile is in read-only mode. Write operations are not allowed.'));
Expand Down Expand Up @@ -240,8 +235,8 @@ program
console.log(`Title: ${chalk.blue(result.title)}`);
console.log(`ID: ${chalk.blue(result.id)}`);
console.log(`Space: ${chalk.blue(result.space.name)} (${result.space.key})`);
console.log(`URL: ${chalk.gray(`${buildPageUrl(config, `/wiki${result._links.webui}`)}`)}`);
console.log(`URL: ${chalk.gray(`${client.buildUrl(`${client.webUrlPrefix}${result._links.webui}`)}`)}`);

analytics.track('create', true);
} catch (error) {
analytics.track('create', false);
Expand Down Expand Up @@ -289,8 +284,8 @@ program
console.log(`ID: ${chalk.blue(result.id)}`);
console.log(`Parent: ${chalk.blue(parentInfo.title)} (${parentId})`);
console.log(`Space: ${chalk.blue(result.space.name)} (${result.space.key})`);
console.log(`URL: ${chalk.gray(`${buildPageUrl(config, `/wiki${result._links.webui}`)}`)}`);
console.log(`URL: ${chalk.gray(`${client.buildUrl(`${client.webUrlPrefix}${result._links.webui}`)}`)}`);

analytics.track('create_child', true);
} catch (error) {
analytics.track('create_child', false);
Expand Down Expand Up @@ -337,8 +332,8 @@ program
console.log(`Title: ${chalk.blue(result.title)}`);
console.log(`ID: ${chalk.blue(result.id)}`);
console.log(`Version: ${chalk.blue(result.version.number)}`);
console.log(`URL: ${chalk.gray(`${buildPageUrl(config, `/wiki${result._links.webui}`)}`)}`);
console.log(`URL: ${chalk.gray(`${client.buildUrl(`${client.webUrlPrefix}${result._links.webui}`)}`)}`);

analytics.track('update', true);
} catch (error) {
analytics.track('update', false);
Expand All @@ -365,7 +360,7 @@ program
console.log(`ID: ${chalk.blue(result.id)}`);
console.log(`New Parent: ${chalk.blue(newParentId)}`);
console.log(`Version: ${chalk.blue(result.version.number)}`);
console.log(`URL: ${chalk.gray(`${buildPageUrl(config, `/wiki${result._links.webui}`)}`)}`);
console.log(`URL: ${chalk.gray(`${client.buildUrl(`${client.webUrlPrefix}${result._links.webui}`)}`)}`);

analytics.track('move', true);
} catch (error) {
Expand Down Expand Up @@ -473,8 +468,8 @@ program
console.log(`Title: ${chalk.green(pageInfo.title)}`);
console.log(`ID: ${chalk.green(pageInfo.id)}`);
console.log(`Space: ${chalk.green(pageInfo.space.name)} (${pageInfo.space.key})`);
console.log(`URL: ${chalk.gray(`${buildPageUrl(config, `/wiki${pageInfo.url}`)}`)}`);
console.log(`URL: ${chalk.gray(`${client.buildUrl(`${client.webUrlPrefix}${pageInfo.url}`)}`)}`);

analytics.track('find', true);
} catch (error) {
analytics.track('find', false);
Expand Down Expand Up @@ -1713,7 +1708,7 @@ program
console.log(` - ...and ${result.failures.length - 10} more`);
}
}
console.log(`URL: ${chalk.gray(`${buildPageUrl(config, `/wiki${result.rootPage._links.webui}`)}`)}`);
console.log(`URL: ${chalk.gray(`${client.buildUrl(`${client.webUrlPrefix}${result.rootPage._links.webui}`)}`)}`);
if (options.failOnError && result.failures?.length) {
analytics.track('copy_tree', false);
console.error(chalk.red('Completed with failures and --fail-on-error is set.'));
Expand Down Expand Up @@ -1775,7 +1770,7 @@ program
type: page.type,
status: page.status,
spaceKey: page.space?.key,
url: `${buildPageUrl(config, `/wiki/spaces/${page.space?.key}/pages/${page.id}`)}`,
url: `${client.buildUrl(`${client.webUrlPrefix}/spaces/${page.space?.key}/pages/${page.id}`)}`,
parentId: page.parentId || resolvedPageId
}))
};
Expand All @@ -1787,7 +1782,7 @@ program

// Build tree structure
const tree = buildTree(children, resolvedPageId);
printTree(tree, config, options, 1);
printTree(tree, client, config, options, 1);

console.log('');
console.log(chalk.gray(`Total: ${children.length} child page${children.length === 1 ? '' : 's'}`));
Expand All @@ -1804,7 +1799,7 @@ program
}

if (options.showUrl) {
const url = `${buildPageUrl(config, `/wiki/spaces/${page.space?.key}/pages/${page.id}`)}`;
const url = `${client.buildUrl(`${client.webUrlPrefix}/spaces/${page.space?.key}/pages/${page.id}`)}`;
output += `\n ${chalk.gray(url)}`;
}

Expand Down Expand Up @@ -1856,7 +1851,7 @@ function buildTree(pages, rootId) {
}

// Helper function to print tree
function printTree(nodes, config, options, depth = 1) {
function printTree(nodes, client, config, options, depth = 1) {
nodes.forEach((node, index) => {
const isLast = index === nodes.length - 1;
const indent = ' '.repeat(depth - 1);
Expand All @@ -1869,14 +1864,14 @@ function printTree(nodes, config, options, depth = 1) {
}

if (options.showUrl) {
const url = `${buildPageUrl(config, `/wiki/spaces/${node.space?.key}/pages/${node.id}`)}`;
const url = `${client.buildUrl(`${client.webUrlPrefix}/spaces/${node.space?.key}/pages/${node.id}`)}`;
output += `\n${indent}${isLast ? ' ' : '│ '}${chalk.gray(url)}`;
}

console.log(output);

if (node.children && node.children.length > 0) {
printTree(node.children, config, options, depth + 1);
printTree(node.children, client, config, options, depth + 1);
}
});
}
Expand Down
9 changes: 5 additions & 4 deletions lib/confluence-client.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ class ConfluenceClient {
this.email = config.email;
this.authType = (config.authType || (this.email ? 'basic' : 'bearer')).toLowerCase();
this.apiPath = this.sanitizeApiPath(config.apiPath);
this.webUrlPrefix = this.apiPath.startsWith('/wiki/') ? '/wiki' : '';
this.baseURL = `${this.protocol}://${this.domain}${this.apiPath}`;
this.markdown = new MarkdownIt();
this.setupConfluenceMarkdownExtensions();
Expand Down Expand Up @@ -412,7 +413,7 @@ class ConfluenceClient {
const webui = page._links?.webui || '';
return {
title: page.title,
url: webui ? this.buildUrl(`/wiki${webui}`) : ''
url: webui ? this.buildUrl(`${this.webUrlPrefix}${webui}`) : ''
};
}
return null;
Expand Down Expand Up @@ -506,7 +507,7 @@ class ConfluenceClient {
// Format: - [Page Title](URL)
const childPagesList = childPages.map(page => {
const webui = page._links?.webui || '';
const url = webui ? this.buildUrl(`/wiki${webui}`) : '';
const url = webui ? this.buildUrl(`${this.webUrlPrefix}${webui}`) : '';
if (url) {
return `- [${page.title}](${url})`;
} else {
Expand Down Expand Up @@ -1351,11 +1352,11 @@ class ConfluenceClient {
// Try to build a proper URL - if spaceKey starts with ~, it's a user space
if (spaceKey.startsWith('~')) {
const spacePath = `display/${spaceKey}/${encodeURIComponent(title)}`;
return `\n> 📄 **${labels.includePage}**: [${title}](${this.buildUrl(`/wiki/${spacePath}`)})\n`;
return `\n> 📄 **${labels.includePage}**: [${title}](${this.buildUrl(`${this.webUrlPrefix}/${spacePath}`)})\n`;
} else {
// For non-user spaces, we cannot construct a valid link without the page ID.
// Document that manual correction is required.
return `\n> 📄 **${labels.includePage}**: [${title}](${this.buildUrl(`/wiki/spaces/${spaceKey}/pages/[PAGE_ID_HERE]`)}) _(manual link correction required)_\n`;
return `\n> 📄 **${labels.includePage}**: [${title}](${this.buildUrl(`${this.webUrlPrefix}/spaces/${spaceKey}/pages/[PAGE_ID_HERE]`)}) _(manual link correction required)_\n`;
}
});

Expand Down
39 changes: 39 additions & 0 deletions tests/confluence-client.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,45 @@ describe('ConfluenceClient', () => {

expect(customClient.baseURL).toBe('https://cloud.example/wiki/rest/api');
});

test('sets webUrlPrefix to /wiki when apiPath starts with /wiki/', () => {
const cloudClient = new ConfluenceClient({
domain: 'test.atlassian.net',
token: 'cloud-token',
apiPath: '/wiki/rest/api'
});

expect(cloudClient.webUrlPrefix).toBe('/wiki');
});

test('sets webUrlPrefix to empty string when apiPath does not start with /wiki/', () => {
const serverClient = new ConfluenceClient({
domain: 'confluence.example.com',
token: 'server-token',
apiPath: '/rest/api'
});

expect(serverClient.webUrlPrefix).toBe('');
});

test('sets webUrlPrefix to empty string when apiPath is not provided', () => {
const defaultClient = new ConfluenceClient({
domain: 'example.com',
token: 'default-token'
});

expect(defaultClient.webUrlPrefix).toBe('');
});

test('sets webUrlPrefix to /wiki when apiPath is wiki/rest/api/ (missing leading slash)', () => {
const clientWithMissingSlash = new ConfluenceClient({
domain: 'confluence.example.com',
token: 'test-token',
apiPath: 'wiki/rest/api/'
});

expect(clientWithMissingSlash.webUrlPrefix).toBe('/wiki');
});
});

describe('authentication setup', () => {
Expand Down
Loading