Skip to content

Commit fb139c2

Browse files
committed
port over
1 parent 276c079 commit fb139c2

File tree

3 files changed

+67
-73
lines changed

3 files changed

+67
-73
lines changed

README.md

Lines changed: 15 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -143,29 +143,25 @@ account Administrator, in order to publish the contents.
143143

144144
### Publishing policies and procedures to Confluence
145145

146-
You can also publish the policies to a Confluence wiki space. Simply run the
147-
`psp publish` command with the `--confluence` option.
146+
You can also publish the policies to a Confluence Cloud wiki space. Simply run
147+
the `psp publish` command with the `--confluence` option and provide necessary
148+
configuration options for non-interactive publishing:
148149

149150
```bash
150-
psp publish --confluence
151+
psp publish --confluence --site <subdomain> --space <KEY> -u <username/email> -k <key/password> -p <parent page id> --debug
151152
```
152153

153-
You will be prompted to enter your Confluence domain and space key, and
154-
username/password:
155-
156-
```bash
157-
? Confluence domain (the vanity subdomain before '.atlassian.net'):
158-
? Confluence space key:
159-
? Confluence username:
160-
? Confluence password: [hidden]
161-
Published 35 docs to Confluence.
162-
```
163-
164-
Or, provide necessary configuration options for non-interactive publishing:
165-
166-
```bash
167-
psp publish --confluence --site <subdomain> --space <KEY> --docs <path> -u <username/email> -k <key/password>
168-
```
154+
To see all available options for Confluence exports run the `psp publish --help`
155+
command.
156+
157+
| Option | Description | Type | Required | Default |
158+
| -------- | ------------------------------------------------------------------- | ------- | -------- | --------------------- |
159+
| site | the vanity domain for the site `<site>.atlassian.net` | string | yes | |
160+
| space | the site space key to create the pages in, case-sensitive | string | yes | |
161+
| username | username for the upload | string | yes | |
162+
| password | password (or API token) for the upload | string | yes | |
163+
| parent | the parent id for all uploaded pages | string | no | Homepage of the space |
164+
| debug | dump the generated Confluence HTML to a tempdir for troubleshooting | boolean | no | false |
169165

170166
The program will save the page ID for each published policy document locally to
171167
a file in the current directory: `confluence-pages.json`. Make sure this file is

src/commands/psp-publish.ts

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ type ProgramInput = {
3737
site?: string;
3838
space?: string;
3939
docs?: string;
40+
parent?: string;
41+
debug?: boolean;
4042
wait?: boolean;
4143
};
4244

@@ -69,22 +71,28 @@ export async function run() {
6971
)
7072
.option('--space <spaceKey>', 'Space key of the Confluence wiki space')
7173
.option(
72-
'-d, --docs [dir]',
73-
'path to docs; used in conjunction with --confluence option',
74-
'docs'
74+
'-d, --docs <dir>',
75+
'path to docs; used in conjunction with --confluence option'
76+
)
77+
.option('-p, --parent <id>', 'Parent page ID for confluence export')
78+
.option(
79+
'-d, --debug',
80+
'Dump generated confluence html to a tempdir for troubleshooting'
7581
)
7682
.parse(process.argv)
7783
.opts() as ProgramInput;
7884

79-
if (program.confluence && program.docs) {
85+
if (program.confluence) {
8086
if (program.site && program.space && program.user && program.apiToken) {
8187
const options: PublishToConfluenceOptions = {
8288
domain: program.site,
8389
space: program.space,
8490
username: program.user,
8591
password: program.apiToken,
92+
parent: program.parent ? program.parent : '',
93+
debug: program.debug ? true : false,
8694
};
87-
await publishToConfluence(program.docs, options);
95+
await publishToConfluence(program.docs ? program.docs : '', options);
8896
process.exit(0);
8997
} else {
9098
console.log(chalk.red('Missing required arguments'));

src/publishToConfluence.ts

Lines changed: 39 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
1-
import { prompt } from 'inquirer';
2-
import path from 'path';
31
import fs from 'fs-extra';
42
import fetch from 'node-fetch';
3+
import os from 'os';
4+
import path from 'path';
55
import showdown from 'showdown';
66
import * as error from '~/src/error';
77

88
const converter = new showdown.Converter({
99
parseImgDimensions: true,
1010
simplifiedAutoLink: true,
1111
tables: true,
12+
disableForced4SpacesIndentedSublists: true,
13+
ghCompatibleHeaderId: true,
1214
});
1315

1416
const CONFLUENCE_PAGES = './confluence-pages.json';
@@ -23,51 +25,25 @@ export type PublishToConfluenceOptions = {
2325
space: string;
2426
username: string;
2527
password: string;
28+
parent: string;
29+
debug: boolean;
2630
};
2731

28-
async function gatherCreds() {
29-
const answer = await prompt([
30-
{
31-
type: 'input',
32-
name: 'domain',
33-
message:
34-
"Confluence domain (the vanity subdomain before '.atlassian.net'):",
35-
},
36-
{
37-
type: 'input',
38-
name: 'space',
39-
message: 'Confluence space key:',
40-
},
41-
{
42-
type: 'input',
43-
name: 'username',
44-
message: 'Confluence username:',
45-
},
46-
{
47-
type: 'password',
48-
name: 'password',
49-
message: 'Confluence password:',
50-
},
51-
]);
52-
return {
53-
domain: answer.domain,
54-
space: answer.space,
55-
username: answer.username,
56-
password: answer.password,
57-
};
58-
}
59-
6032
function parseLinks(
6133
pageUrl: string,
6234
html: string,
6335
confluencePages: Record<string, string>
6436
) {
65-
const linkRegex = /href=['"]([\w-]+\.md)(#.*)?['"]/gm;
66-
const match = linkRegex.exec(html);
67-
68-
return match
69-
? html.replace(linkRegex, `href="${pageUrl}/${confluencePages[match[1]]}"`)
70-
: html;
37+
const linkRegex: RegExp = /href=['"]([\w-]+\.md)(#.*)?['"]/gm;
38+
const hasLinks = linkRegex.test(html);
39+
40+
if (hasLinks) {
41+
return html.replace(linkRegex, (match, p1, p2 = '') => {
42+
return `href="${pageUrl}/${confluencePages[p1]}${p2.toLowerCase()}"`;
43+
});
44+
} else {
45+
return html;
46+
}
7147
}
7248

7349
async function getVersion(headers: Record<string, string>, page: string) {
@@ -83,20 +59,20 @@ export default async function publishToConfluence(
8359
source: string,
8460
options: PublishToConfluenceOptions
8561
) {
86-
const docsPath = source || path.join(__dirname, '../docs');
62+
const docsPath = source || './docs';
8763
if (!fs.existsSync(docsPath)) {
8864
error.fatal('Please run `psp build` first to generate the policy docs.');
8965
}
9066

91-
const { domain, space, username, password } =
92-
options || (await gatherCreds());
67+
const { domain, space, username, password, parent, debug } = options;
9368

9469
const site = `https://${domain || CONFLUENCE_DOMAIN}.atlassian.net`;
9570
const baseUrl = `${site}/wiki/rest/api/content`;
9671
const pageUrl = `${site}/wiki/spaces/${space || CONFLUENCE_SPACE}/pages`;
9772

9873
const headers = {
9974
'Content-Type': 'application/json',
75+
10076
Accept: 'application/json',
10177
Authorization: `Basic ${Buffer.from(
10278
(username || CONFLUENCE_USER) + ':' + (password || CONFLUENCE_PASS)
@@ -109,6 +85,14 @@ export default async function publishToConfluence(
10985

11086
const worked = [];
11187
const failed = [];
88+
let debugPath = '';
89+
90+
if (debug) {
91+
debugPath = await fs.mkdtemp(path.join(os.tmpdir(), 'confluence-html-'));
92+
console.log(
93+
`Debug enabled, generated confluence html can be found in ${debugPath}`
94+
);
95+
}
11296

11397
const docs = fs.readdirSync(docsPath);
11498

@@ -121,8 +105,8 @@ export default async function publishToConfluence(
121105
if (doc.endsWith('.md')) {
122106
const data = fs.readFileSync(path.join(docsPath, doc), 'utf8');
123107
const parsedData = data
108+
.replace(/#([\w-]+)/gm, '$&'.toLowerCase())
124109
.replace(/^#(.*)$/m, '') // removes title
125-
.replace(/^ {2}(-|\*)/gm, ' -') // fixes sublist indentation
126110
.replace(/&/gm, '&amp;')
127111
.replace(/[]/gm, `'`) // fixes quote character
128112
.replace(/[]/gm, `"`);
@@ -143,13 +127,14 @@ export default async function publishToConfluence(
143127
}
144128
const title = match[1].trim();
145129

146-
const body = {
130+
const req = {
147131
version,
148132
type: 'page',
149133
title,
150134
space: {
151135
key: space || CONFLUENCE_SPACE,
152136
},
137+
ancestors: parent ? [{ id: parent }] : [],
153138
body: {
154139
storage: {
155140
value: parsedHtml,
@@ -161,20 +146,25 @@ export default async function publishToConfluence(
161146
const options = {
162147
method: pageId ? 'put' : 'post',
163148
headers,
164-
body: JSON.stringify(body),
149+
body: JSON.stringify(req),
165150
};
166151

167152
const uri = pageId ? `${baseUrl}/${pageId}` : baseUrl;
168153
const response = await fetch(uri, options);
154+
const result = await response.json();
169155
if (response.ok) {
170-
const result = await response.json();
171156
confluencePages[doc] = pageId || result.id;
157+
if (debug) {
158+
fs.writeFileSync(`${debugPath}/${doc}.html`, parsedHtml);
159+
}
172160
worked.push(doc);
173161
} else {
174162
failed.push(doc);
175-
fs.writeFileSync(`./failed-${doc}.html`, parsedHtml);
163+
if (debug) {
164+
fs.writeFileSync(`${debugPath}/failed-${doc}.html`, parsedHtml);
165+
}
176166
console.error(`publish to confluence failed for ${doc}`);
177-
console.error({ response: await response.json() });
167+
console.error(result.message);
178168
continue;
179169
}
180170
}

0 commit comments

Comments
 (0)