@repo-toolkit/confluence 0.14.2 → 0.15.0
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/README.md +4 -0
- package/cli.js +105 -6
- package/index.d.ts +3 -0
- package/index.js +83 -5
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -72,6 +72,9 @@ Flags:
|
|
|
72
72
|
- `--space-key <key>` — Confluence space key (required; resolved to a `spaceId`)
|
|
73
73
|
- `--parent-page-id <id>` — numeric Confluence page id (required)
|
|
74
74
|
- `--version-message <text>` — version-message suffix appended to every PUT
|
|
75
|
+
- `--repository-url <url>` — repository URL appended to synced pages as an
|
|
76
|
+
italic source notice. When omitted, GitHub Actions runs infer this from
|
|
77
|
+
`GITHUB_SERVER_URL` and `GITHUB_REPOSITORY`.
|
|
75
78
|
- `--skip-unchanged` / `--no-skip-unchanged` — skip pages whose body is unchanged (default: `skip`)
|
|
76
79
|
- `--dry-run` — walk the doc tree and validate every markdown file and local
|
|
77
80
|
image source (same preflight as a real sync) then log the plan. No API
|
|
@@ -99,6 +102,7 @@ both read for every option. Boolean env values accept `true|1|yes|on` /
|
|
|
99
102
|
| spaceKey | `CONFLUENCE_SPACE_KEY` | `INPUT_SPACE-KEY` |
|
|
100
103
|
| parentPageId | `CONFLUENCE_PARENT_PAGE_ID` | `INPUT_PARENT-PAGE-ID` |
|
|
101
104
|
| versionMessage | `CONFLUENCE_VERSION_MESSAGE` | `INPUT_VERSION-MESSAGE` |
|
|
105
|
+
| repositoryUrl | `CONFLUENCE_REPOSITORY_URL` | `INPUT_REPOSITORY-URL` |
|
|
102
106
|
| skipUnchanged (bool) | `CONFLUENCE_SKIP_UNCHANGED` | `INPUT_SKIP-UNCHANGED` |
|
|
103
107
|
| dryRun (bool) | `CONFLUENCE_DRY_RUN` | `INPUT_DRY-RUN` |
|
|
104
108
|
| renderHtmlBlocks (bool) | `CONFLUENCE_RENDER_HTML_BLOCKS` | `INPUT_RENDER-HTML-BLOCKS` |
|
package/cli.js
CHANGED
|
@@ -720,13 +720,22 @@ function markdownToStorage(markdown, options = {}) {
|
|
|
720
720
|
out.push(`<blockquote>${renderInline(quoteLines.join("\n"))}</blockquote>`);
|
|
721
721
|
continue;
|
|
722
722
|
}
|
|
723
|
+
if (isTableStart(lines, i)) {
|
|
724
|
+
const tableLines = [];
|
|
725
|
+
while (i < lines.length && typeof lines[i] === "string" && isTableRow(lines[i])) {
|
|
726
|
+
tableLines.push(lines[i]);
|
|
727
|
+
i += 1;
|
|
728
|
+
}
|
|
729
|
+
out.push(renderTable(tableLines));
|
|
730
|
+
continue;
|
|
731
|
+
}
|
|
723
732
|
if (/^\s{0,3}---+\s*$/.test(line) || /^\s{0,3}\*\*\*+\s*$/.test(line)) {
|
|
724
733
|
out.push("<hr />");
|
|
725
734
|
i += 1;
|
|
726
735
|
continue;
|
|
727
736
|
}
|
|
728
737
|
const para = [];
|
|
729
|
-
while (i < lines.length && typeof lines[i] === "string" && lines[i].trim() !== "" && !/^\s{0,3}#{1,6}\s/.test(lines[i]) && !/^\s{0,3}```/.test(lines[i]) && !/^\s{0,3}(?:-|\*|\+)\s+/.test(lines[i]) && !/^\s{0,3}\d+\.\s+/.test(lines[i]) && !/^\s{0,3}>/.test(lines[i]) && !/^\s{0,3}---+\s*$/.test(lines[i]) && !/^\s{0,3}\*\*\*+\s*$/.test(lines[i])) {
|
|
738
|
+
while (i < lines.length && typeof lines[i] === "string" && lines[i].trim() !== "" && !/^\s{0,3}#{1,6}\s/.test(lines[i]) && !/^\s{0,3}```/.test(lines[i]) && !/^\s{0,3}(?:-|\*|\+)\s+/.test(lines[i]) && !/^\s{0,3}\d+\.\s+/.test(lines[i]) && !/^\s{0,3}>/.test(lines[i]) && !isTableStart(lines, i) && !/^\s{0,3}---+\s*$/.test(lines[i]) && !/^\s{0,3}\*\*\*+\s*$/.test(lines[i])) {
|
|
730
739
|
para.push(lines[i]);
|
|
731
740
|
i += 1;
|
|
732
741
|
}
|
|
@@ -786,6 +795,60 @@ function renderList(listLines) {
|
|
|
786
795
|
const body = items.map((item) => `<li>${renderInline(item)}</li>`).join("");
|
|
787
796
|
return `<${tag}>${body}</${tag}>`;
|
|
788
797
|
}
|
|
798
|
+
function isTableStart(lines, index) {
|
|
799
|
+
const header = lines[index];
|
|
800
|
+
const separator = lines[index + 1];
|
|
801
|
+
if (typeof header !== "string" || typeof separator !== "string") {
|
|
802
|
+
return false;
|
|
803
|
+
}
|
|
804
|
+
if (!isTableRow(header) || !isTableSeparator(separator)) {
|
|
805
|
+
return false;
|
|
806
|
+
}
|
|
807
|
+
return splitTableRow(header).length === splitTableRow(separator).length;
|
|
808
|
+
}
|
|
809
|
+
function isTableRow(line) {
|
|
810
|
+
return /^\s{0,3}\|/.test(line) && splitTableRow(line).length > 1;
|
|
811
|
+
}
|
|
812
|
+
function isTableSeparator(line) {
|
|
813
|
+
const cells = splitTableRow(line);
|
|
814
|
+
if (cells.length < 2) {
|
|
815
|
+
return false;
|
|
816
|
+
}
|
|
817
|
+
return cells.every((cell) => /^:?-{3,}:?$/.test(cell.trim()));
|
|
818
|
+
}
|
|
819
|
+
function splitTableRow(line) {
|
|
820
|
+
const trimmed = line.trim();
|
|
821
|
+
const start = trimmed.startsWith("|") ? 1 : 0;
|
|
822
|
+
const end = trimmed.endsWith("|") && !isEscapedPipe(trimmed, trimmed.length - 1) ? trimmed.length - 1 : trimmed.length;
|
|
823
|
+
const cells = [];
|
|
824
|
+
let cellStart = start;
|
|
825
|
+
for (let i = start; i < end; i += 1) {
|
|
826
|
+
if (trimmed[i] === "|" && !isEscapedPipe(trimmed, i)) {
|
|
827
|
+
cells.push(unescapeTableCell(trimmed.slice(cellStart, i).trim()));
|
|
828
|
+
cellStart = i + 1;
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
cells.push(unescapeTableCell(trimmed.slice(cellStart, end).trim()));
|
|
832
|
+
return cells;
|
|
833
|
+
}
|
|
834
|
+
function isEscapedPipe(text, index) {
|
|
835
|
+
let backslashes = 0;
|
|
836
|
+
for (let i = index - 1; i >= 0 && text[i] === "\\"; i -= 1) {
|
|
837
|
+
backslashes += 1;
|
|
838
|
+
}
|
|
839
|
+
return backslashes % 2 === 1;
|
|
840
|
+
}
|
|
841
|
+
function unescapeTableCell(cell) {
|
|
842
|
+
return cell.replace(/\\\|/g, "|");
|
|
843
|
+
}
|
|
844
|
+
function renderTable(tableLines) {
|
|
845
|
+
const rows = tableLines.filter((_, index) => index !== 1).map(splitTableRow);
|
|
846
|
+
const body = rows.map((cells, rowIndex) => {
|
|
847
|
+
const tag = rowIndex === 0 ? "th" : "td";
|
|
848
|
+
return "<tr>" + cells.map((cell) => "<" + tag + ">" + renderInline(cell) + "</" + tag + ">").join("") + "</tr>";
|
|
849
|
+
}).join("");
|
|
850
|
+
return "<table><tbody>" + body + "</tbody></table>";
|
|
851
|
+
}
|
|
789
852
|
function renderInline(text) {
|
|
790
853
|
const out = [];
|
|
791
854
|
const tokens = tokenizeInline(text, 0, text.length);
|
|
@@ -1558,16 +1621,17 @@ function validateLocalSync(entries, plan) {
|
|
|
1558
1621
|
const { html, mermaidBlocks } = markdownToStorage(markdown, {
|
|
1559
1622
|
renderHtmlBlocks: plan.renderHtmlBlocks
|
|
1560
1623
|
});
|
|
1624
|
+
const body = appendRepositoryNotice(html, plan.repositoryUrl);
|
|
1561
1625
|
const markdownDir = dirname(entry.absolute);
|
|
1562
|
-
const hasLocalImages = hasLocalImagePlaceholder(
|
|
1626
|
+
const hasLocalImages = hasLocalImagePlaceholder(body);
|
|
1563
1627
|
const hasMermaidBlocks = mermaidBlocks.length > 0;
|
|
1564
|
-
const attachments = hasLocalImages ? validateAttachmentSources(
|
|
1628
|
+
const attachments = hasLocalImages ? validateAttachmentSources(body, {
|
|
1565
1629
|
markdownDir,
|
|
1566
1630
|
allowedRoot: plan.folder
|
|
1567
1631
|
}) : [];
|
|
1568
1632
|
plans.push({
|
|
1569
1633
|
entry,
|
|
1570
|
-
html,
|
|
1634
|
+
html: body,
|
|
1571
1635
|
mermaidBlocks,
|
|
1572
1636
|
markdownDir,
|
|
1573
1637
|
hasLocalImages,
|
|
@@ -1624,6 +1688,11 @@ function resolveConfluenceSyncPlan(options = {}) {
|
|
|
1624
1688
|
throw new Error(`parentPageId must be numeric, got: ${options.parentPageId}`);
|
|
1625
1689
|
}
|
|
1626
1690
|
}
|
|
1691
|
+
if (options.repositoryUrl && !isAllowedUrl(options.repositoryUrl)) {
|
|
1692
|
+
throw new Error(
|
|
1693
|
+
"repositoryUrl must be an http(s), protocol-relative, server-relative, mailto, tel, or relative URL"
|
|
1694
|
+
);
|
|
1695
|
+
}
|
|
1627
1696
|
return {
|
|
1628
1697
|
cwd,
|
|
1629
1698
|
folder,
|
|
@@ -1635,7 +1704,8 @@ function resolveConfluenceSyncPlan(options = {}) {
|
|
|
1635
1704
|
versionMessage: options.versionMessage ?? "Synced via repo-toolkit-confluence",
|
|
1636
1705
|
skipUnchanged: options.skipUnchanged ?? true,
|
|
1637
1706
|
dryRun: options.dryRun ?? false,
|
|
1638
|
-
renderHtmlBlocks: options.renderHtmlBlocks === true
|
|
1707
|
+
renderHtmlBlocks: options.renderHtmlBlocks === true,
|
|
1708
|
+
repositoryUrl: options.repositoryUrl ?? ""
|
|
1639
1709
|
};
|
|
1640
1710
|
}
|
|
1641
1711
|
async function syncConfluenceToDocs(options = {}) {
|
|
@@ -1831,6 +1901,14 @@ function hasLocalImagePlaceholder(html) {
|
|
|
1831
1901
|
LOCAL_IMAGE_PLACEHOLDER_RE.lastIndex = 0;
|
|
1832
1902
|
return LOCAL_IMAGE_PLACEHOLDER_RE.test(html);
|
|
1833
1903
|
}
|
|
1904
|
+
function appendRepositoryNotice(html, repositoryUrl) {
|
|
1905
|
+
if (repositoryUrl.length === 0) {
|
|
1906
|
+
return html;
|
|
1907
|
+
}
|
|
1908
|
+
const url = escapeXmlAttribute(repositoryUrl);
|
|
1909
|
+
const text = escapeHtml(repositoryUrl);
|
|
1910
|
+
return html + '\n<p><em>This document is synced from repository <a href="' + url + '">' + text + "</a>.</em></p>";
|
|
1911
|
+
}
|
|
1834
1912
|
async function predictBody(html, mermaidBlocks, pageId, client, ctx) {
|
|
1835
1913
|
let predicted = html;
|
|
1836
1914
|
if (ctx.hasMermaidBlocks) {
|
|
@@ -1884,6 +1962,7 @@ var SPECS = [
|
|
|
1884
1962
|
{ name: "space-key" },
|
|
1885
1963
|
{ name: "parent-page-id" },
|
|
1886
1964
|
{ name: "version-message" },
|
|
1965
|
+
{ name: "repository-url" },
|
|
1887
1966
|
{ name: "skip-unchanged", boolean: true, negatable: true },
|
|
1888
1967
|
{ name: "dry-run", boolean: true },
|
|
1889
1968
|
{ name: "render-html-blocks", boolean: true },
|
|
@@ -1928,6 +2007,7 @@ Environment variables (CLI form; GitHub Action INPUT_* form is also read):
|
|
|
1928
2007
|
CONFLUENCE_SPACE_KEY Confluence space key
|
|
1929
2008
|
CONFLUENCE_PARENT_PAGE_ID Numeric parent page id
|
|
1930
2009
|
CONFLUENCE_VERSION_MESSAGE Version-message suffix for every PUT
|
|
2010
|
+
CONFLUENCE_REPOSITORY_URL Repository URL appended to synced pages
|
|
1931
2011
|
CONFLUENCE_SKIP_UNCHANGED true|false (default: true)
|
|
1932
2012
|
CONFLUENCE_DRY_RUN true|false (default: false)
|
|
1933
2013
|
CONFLUENCE_RENDER_HTML_BLOCKS true|false (default: false)
|
|
@@ -1947,6 +2027,7 @@ Options:
|
|
|
1947
2027
|
--space-key <key> Confluence space key (required). Resolved to a spaceId via the API
|
|
1948
2028
|
--parent-page-id <id> Numeric page id under which docs will be published (required)
|
|
1949
2029
|
--version-message <text> Commit message appended to every page/attachment PUT
|
|
2030
|
+
--repository-url <url> Repository URL appended to synced pages as an italic notice
|
|
1950
2031
|
--skip-unchanged Skip pages whose body is unchanged (default: true)
|
|
1951
2032
|
--no-skip-unchanged Re-upload every page even when unchanged
|
|
1952
2033
|
--dry-run Walk the doc tree and print the plan without API calls
|
|
@@ -1965,7 +2046,8 @@ var STRING_OPTION_KEYS = [
|
|
|
1965
2046
|
"baseUrl",
|
|
1966
2047
|
"spaceKey",
|
|
1967
2048
|
"parentPageId",
|
|
1968
|
-
"versionMessage"
|
|
2049
|
+
"versionMessage",
|
|
2050
|
+
"repositoryUrl"
|
|
1969
2051
|
];
|
|
1970
2052
|
function setIfString(options, key, value) {
|
|
1971
2053
|
if (typeof value === "string" && value.length > 0) {
|
|
@@ -1986,6 +2068,7 @@ var ENV_BINDINGS = [
|
|
|
1986
2068
|
{ envName: "INPUT_SPACE-KEY", key: "spaceKey", kind: "string" },
|
|
1987
2069
|
{ envName: "INPUT_PARENT-PAGE-ID", key: "parentPageId", kind: "string" },
|
|
1988
2070
|
{ envName: "INPUT_VERSION-MESSAGE", key: "versionMessage", kind: "string" },
|
|
2071
|
+
{ envName: "INPUT_REPOSITORY-URL", key: "repositoryUrl", kind: "string" },
|
|
1989
2072
|
{ envName: "INPUT_DRY-RUN", key: "dryRun", kind: "boolean" },
|
|
1990
2073
|
{ envName: "INPUT_SKIP-UNCHANGED", key: "skipUnchanged", kind: "boolean" },
|
|
1991
2074
|
{ envName: "INPUT_RENDER-HTML-BLOCKS", key: "renderHtmlBlocks", kind: "boolean" },
|
|
@@ -1997,6 +2080,7 @@ var ENV_BINDINGS = [
|
|
|
1997
2080
|
{ envName: "CONFLUENCE_SPACE_KEY", key: "spaceKey", kind: "string" },
|
|
1998
2081
|
{ envName: "CONFLUENCE_PARENT_PAGE_ID", key: "parentPageId", kind: "string" },
|
|
1999
2082
|
{ envName: "CONFLUENCE_VERSION_MESSAGE", key: "versionMessage", kind: "string" },
|
|
2083
|
+
{ envName: "CONFLUENCE_REPOSITORY_URL", key: "repositoryUrl", kind: "string" },
|
|
2000
2084
|
{ envName: "CONFLUENCE_DRY_RUN", key: "dryRun", kind: "boolean" },
|
|
2001
2085
|
{ envName: "CONFLUENCE_SKIP_UNCHANGED", key: "skipUnchanged", kind: "boolean" },
|
|
2002
2086
|
{ envName: "CONFLUENCE_RENDER_HTML_BLOCKS", key: "renderHtmlBlocks", kind: "boolean" }
|
|
@@ -2017,8 +2101,22 @@ function optionsFromEnv(env = process.env) {
|
|
|
2017
2101
|
options[key] = parseBooleanEnv(raw, envName);
|
|
2018
2102
|
}
|
|
2019
2103
|
}
|
|
2104
|
+
if (options.repositoryUrl === void 0) {
|
|
2105
|
+
const githubRepositoryUrl = repositoryUrlFromGitHubEnv(env);
|
|
2106
|
+
if (githubRepositoryUrl) {
|
|
2107
|
+
options.repositoryUrl = githubRepositoryUrl;
|
|
2108
|
+
}
|
|
2109
|
+
}
|
|
2020
2110
|
return options;
|
|
2021
2111
|
}
|
|
2112
|
+
function repositoryUrlFromGitHubEnv(env) {
|
|
2113
|
+
const repository = env.GITHUB_REPOSITORY;
|
|
2114
|
+
if (!repository) {
|
|
2115
|
+
return void 0;
|
|
2116
|
+
}
|
|
2117
|
+
const serverUrl = env.GITHUB_SERVER_URL || "https://github.com";
|
|
2118
|
+
return serverUrl.replace(/\/+$/, "") + "/" + repository.replace(/^\/+/, "");
|
|
2119
|
+
}
|
|
2022
2120
|
function buildOptions(result) {
|
|
2023
2121
|
if (!result) {
|
|
2024
2122
|
return {};
|
|
@@ -2034,6 +2132,7 @@ function buildOptions(result) {
|
|
|
2034
2132
|
setIfString(options, "spaceKey", values["space-key"]);
|
|
2035
2133
|
setIfString(options, "parentPageId", values["parent-page-id"]);
|
|
2036
2134
|
setIfString(options, "versionMessage", values["version-message"]);
|
|
2135
|
+
setIfString(options, "repositoryUrl", values["repository-url"]);
|
|
2037
2136
|
if (values["skip-unchanged"] !== void 0) {
|
|
2038
2137
|
options.skipUnchanged = values["skip-unchanged"] === "true";
|
|
2039
2138
|
}
|
package/index.d.ts
CHANGED
|
@@ -338,6 +338,8 @@ interface ConfluenceSyncOptions {
|
|
|
338
338
|
skipUnchanged?: boolean;
|
|
339
339
|
/** Render ```html fenced blocks as inline HTML via the Confluence `html` macro instead of a code box (default: false). */
|
|
340
340
|
renderHtmlBlocks?: boolean;
|
|
341
|
+
/** Repository URL appended to synced pages as an italic source notice. */
|
|
342
|
+
repositoryUrl?: string;
|
|
341
343
|
/**
|
|
342
344
|
* Dry-run: walk the tree and validate every markdown file and local image
|
|
343
345
|
* source (same preflight as a real sync) then print the plan, but make no
|
|
@@ -367,6 +369,7 @@ interface ConfluenceSyncPlan {
|
|
|
367
369
|
skipUnchanged: boolean;
|
|
368
370
|
dryRun: boolean;
|
|
369
371
|
renderHtmlBlocks: boolean;
|
|
372
|
+
repositoryUrl: string;
|
|
370
373
|
}
|
|
371
374
|
/** A single markdown entry's locally-validated sync plan. */
|
|
372
375
|
interface LocalSyncEntryPlan {
|
package/index.js
CHANGED
|
@@ -706,13 +706,22 @@ function markdownToStorage(markdown, options = {}) {
|
|
|
706
706
|
out.push(`<blockquote>${renderInline(quoteLines.join("\n"))}</blockquote>`);
|
|
707
707
|
continue;
|
|
708
708
|
}
|
|
709
|
+
if (isTableStart(lines, i)) {
|
|
710
|
+
const tableLines = [];
|
|
711
|
+
while (i < lines.length && typeof lines[i] === "string" && isTableRow(lines[i])) {
|
|
712
|
+
tableLines.push(lines[i]);
|
|
713
|
+
i += 1;
|
|
714
|
+
}
|
|
715
|
+
out.push(renderTable(tableLines));
|
|
716
|
+
continue;
|
|
717
|
+
}
|
|
709
718
|
if (/^\s{0,3}---+\s*$/.test(line) || /^\s{0,3}\*\*\*+\s*$/.test(line)) {
|
|
710
719
|
out.push("<hr />");
|
|
711
720
|
i += 1;
|
|
712
721
|
continue;
|
|
713
722
|
}
|
|
714
723
|
const para = [];
|
|
715
|
-
while (i < lines.length && typeof lines[i] === "string" && lines[i].trim() !== "" && !/^\s{0,3}#{1,6}\s/.test(lines[i]) && !/^\s{0,3}```/.test(lines[i]) && !/^\s{0,3}(?:-|\*|\+)\s+/.test(lines[i]) && !/^\s{0,3}\d+\.\s+/.test(lines[i]) && !/^\s{0,3}>/.test(lines[i]) && !/^\s{0,3}---+\s*$/.test(lines[i]) && !/^\s{0,3}\*\*\*+\s*$/.test(lines[i])) {
|
|
724
|
+
while (i < lines.length && typeof lines[i] === "string" && lines[i].trim() !== "" && !/^\s{0,3}#{1,6}\s/.test(lines[i]) && !/^\s{0,3}```/.test(lines[i]) && !/^\s{0,3}(?:-|\*|\+)\s+/.test(lines[i]) && !/^\s{0,3}\d+\.\s+/.test(lines[i]) && !/^\s{0,3}>/.test(lines[i]) && !isTableStart(lines, i) && !/^\s{0,3}---+\s*$/.test(lines[i]) && !/^\s{0,3}\*\*\*+\s*$/.test(lines[i])) {
|
|
716
725
|
para.push(lines[i]);
|
|
717
726
|
i += 1;
|
|
718
727
|
}
|
|
@@ -772,6 +781,60 @@ function renderList(listLines) {
|
|
|
772
781
|
const body = items.map((item) => `<li>${renderInline(item)}</li>`).join("");
|
|
773
782
|
return `<${tag}>${body}</${tag}>`;
|
|
774
783
|
}
|
|
784
|
+
function isTableStart(lines, index) {
|
|
785
|
+
const header = lines[index];
|
|
786
|
+
const separator = lines[index + 1];
|
|
787
|
+
if (typeof header !== "string" || typeof separator !== "string") {
|
|
788
|
+
return false;
|
|
789
|
+
}
|
|
790
|
+
if (!isTableRow(header) || !isTableSeparator(separator)) {
|
|
791
|
+
return false;
|
|
792
|
+
}
|
|
793
|
+
return splitTableRow(header).length === splitTableRow(separator).length;
|
|
794
|
+
}
|
|
795
|
+
function isTableRow(line) {
|
|
796
|
+
return /^\s{0,3}\|/.test(line) && splitTableRow(line).length > 1;
|
|
797
|
+
}
|
|
798
|
+
function isTableSeparator(line) {
|
|
799
|
+
const cells = splitTableRow(line);
|
|
800
|
+
if (cells.length < 2) {
|
|
801
|
+
return false;
|
|
802
|
+
}
|
|
803
|
+
return cells.every((cell) => /^:?-{3,}:?$/.test(cell.trim()));
|
|
804
|
+
}
|
|
805
|
+
function splitTableRow(line) {
|
|
806
|
+
const trimmed = line.trim();
|
|
807
|
+
const start = trimmed.startsWith("|") ? 1 : 0;
|
|
808
|
+
const end = trimmed.endsWith("|") && !isEscapedPipe(trimmed, trimmed.length - 1) ? trimmed.length - 1 : trimmed.length;
|
|
809
|
+
const cells = [];
|
|
810
|
+
let cellStart = start;
|
|
811
|
+
for (let i = start; i < end; i += 1) {
|
|
812
|
+
if (trimmed[i] === "|" && !isEscapedPipe(trimmed, i)) {
|
|
813
|
+
cells.push(unescapeTableCell(trimmed.slice(cellStart, i).trim()));
|
|
814
|
+
cellStart = i + 1;
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
cells.push(unescapeTableCell(trimmed.slice(cellStart, end).trim()));
|
|
818
|
+
return cells;
|
|
819
|
+
}
|
|
820
|
+
function isEscapedPipe(text, index) {
|
|
821
|
+
let backslashes = 0;
|
|
822
|
+
for (let i = index - 1; i >= 0 && text[i] === "\\"; i -= 1) {
|
|
823
|
+
backslashes += 1;
|
|
824
|
+
}
|
|
825
|
+
return backslashes % 2 === 1;
|
|
826
|
+
}
|
|
827
|
+
function unescapeTableCell(cell) {
|
|
828
|
+
return cell.replace(/\\\|/g, "|");
|
|
829
|
+
}
|
|
830
|
+
function renderTable(tableLines) {
|
|
831
|
+
const rows = tableLines.filter((_, index) => index !== 1).map(splitTableRow);
|
|
832
|
+
const body = rows.map((cells, rowIndex) => {
|
|
833
|
+
const tag = rowIndex === 0 ? "th" : "td";
|
|
834
|
+
return "<tr>" + cells.map((cell) => "<" + tag + ">" + renderInline(cell) + "</" + tag + ">").join("") + "</tr>";
|
|
835
|
+
}).join("");
|
|
836
|
+
return "<table><tbody>" + body + "</tbody></table>";
|
|
837
|
+
}
|
|
775
838
|
function renderInline(text) {
|
|
776
839
|
const out = [];
|
|
777
840
|
const tokens = tokenizeInline(text, 0, text.length);
|
|
@@ -1545,16 +1608,17 @@ function validateLocalSync(entries, plan) {
|
|
|
1545
1608
|
const { html, mermaidBlocks } = markdownToStorage(markdown, {
|
|
1546
1609
|
renderHtmlBlocks: plan.renderHtmlBlocks
|
|
1547
1610
|
});
|
|
1611
|
+
const body = appendRepositoryNotice(html, plan.repositoryUrl);
|
|
1548
1612
|
const markdownDir = dirname(entry.absolute);
|
|
1549
|
-
const hasLocalImages = hasLocalImagePlaceholder(
|
|
1613
|
+
const hasLocalImages = hasLocalImagePlaceholder(body);
|
|
1550
1614
|
const hasMermaidBlocks = mermaidBlocks.length > 0;
|
|
1551
|
-
const attachments = hasLocalImages ? validateAttachmentSources(
|
|
1615
|
+
const attachments = hasLocalImages ? validateAttachmentSources(body, {
|
|
1552
1616
|
markdownDir,
|
|
1553
1617
|
allowedRoot: plan.folder
|
|
1554
1618
|
}) : [];
|
|
1555
1619
|
plans.push({
|
|
1556
1620
|
entry,
|
|
1557
|
-
html,
|
|
1621
|
+
html: body,
|
|
1558
1622
|
mermaidBlocks,
|
|
1559
1623
|
markdownDir,
|
|
1560
1624
|
hasLocalImages,
|
|
@@ -1611,6 +1675,11 @@ function resolveConfluenceSyncPlan(options = {}) {
|
|
|
1611
1675
|
throw new Error(`parentPageId must be numeric, got: ${options.parentPageId}`);
|
|
1612
1676
|
}
|
|
1613
1677
|
}
|
|
1678
|
+
if (options.repositoryUrl && !isAllowedUrl(options.repositoryUrl)) {
|
|
1679
|
+
throw new Error(
|
|
1680
|
+
"repositoryUrl must be an http(s), protocol-relative, server-relative, mailto, tel, or relative URL"
|
|
1681
|
+
);
|
|
1682
|
+
}
|
|
1614
1683
|
return {
|
|
1615
1684
|
cwd,
|
|
1616
1685
|
folder,
|
|
@@ -1622,7 +1691,8 @@ function resolveConfluenceSyncPlan(options = {}) {
|
|
|
1622
1691
|
versionMessage: options.versionMessage ?? "Synced via repo-toolkit-confluence",
|
|
1623
1692
|
skipUnchanged: options.skipUnchanged ?? true,
|
|
1624
1693
|
dryRun: options.dryRun ?? false,
|
|
1625
|
-
renderHtmlBlocks: options.renderHtmlBlocks === true
|
|
1694
|
+
renderHtmlBlocks: options.renderHtmlBlocks === true,
|
|
1695
|
+
repositoryUrl: options.repositoryUrl ?? ""
|
|
1626
1696
|
};
|
|
1627
1697
|
}
|
|
1628
1698
|
async function syncConfluenceToDocs(options = {}) {
|
|
@@ -1818,6 +1888,14 @@ function hasLocalImagePlaceholder(html) {
|
|
|
1818
1888
|
LOCAL_IMAGE_PLACEHOLDER_RE.lastIndex = 0;
|
|
1819
1889
|
return LOCAL_IMAGE_PLACEHOLDER_RE.test(html);
|
|
1820
1890
|
}
|
|
1891
|
+
function appendRepositoryNotice(html, repositoryUrl) {
|
|
1892
|
+
if (repositoryUrl.length === 0) {
|
|
1893
|
+
return html;
|
|
1894
|
+
}
|
|
1895
|
+
const url = escapeXmlAttribute(repositoryUrl);
|
|
1896
|
+
const text = escapeHtml(repositoryUrl);
|
|
1897
|
+
return html + '\n<p><em>This document is synced from repository <a href="' + url + '">' + text + "</a>.</em></p>";
|
|
1898
|
+
}
|
|
1821
1899
|
async function predictBody(html, mermaidBlocks, pageId, client, ctx) {
|
|
1822
1900
|
let predicted = html;
|
|
1823
1901
|
if (ctx.hasMermaidBlocks) {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@repo-toolkit/confluence",
|
|
3
3
|
"description": "Sync a folder of markdown docs to Confluence pages and attachments (GitHub Action compatible)",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.15.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
7
7
|
"keywords": [
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"node": ">=20"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@repo-toolkit/publish-package": "0.
|
|
30
|
+
"@repo-toolkit/publish-package": "0.15.0"
|
|
31
31
|
},
|
|
32
32
|
"files": [
|
|
33
33
|
"**/*",
|