confluence-md-sync 0.6.0 → 0.7.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 +31 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/publish/notice.d.ts +34 -0
- package/dist/publish/notice.js +37 -0
- package/dist/publish/publish.d.ts +8 -0
- package/dist/publish/publish.js +12 -0
- package/dist/publish/runner.d.ts +7 -0
- package/dist/publish/runner.js +5 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -25,6 +25,9 @@ npm install -g confluence-md-sync # as a CLI: `confluence-md-sync …`
|
|
|
25
25
|
- **Attachment dedup** — uploads are tagged `sha256:<hash>`; unchanged files
|
|
26
26
|
are reused. `<file>.src-sha256` sidecars pin dedup to the *source* of
|
|
27
27
|
non-deterministic artifacts (e.g. PNGs rendered by a headless browser).
|
|
28
|
+
- **Managed-page banner** — optionally stamp a “edit this in <source>” notice
|
|
29
|
+
(info/warning panel with a link) into the page; injected into storage only,
|
|
30
|
+
never into the markdown source. See *Managed-page banner*.
|
|
28
31
|
- **Fail before write** — placeholders, files and macro markers are validated
|
|
29
32
|
up front; Confluence is never touched on a broken input.
|
|
30
33
|
- **Pluggable macros** — built-in `core` + `table-filter` plugins, extend
|
|
@@ -121,6 +124,34 @@ await publishPage({
|
|
|
121
124
|
}, cfg);
|
|
122
125
|
```
|
|
123
126
|
|
|
127
|
+
### Managed-page banner
|
|
128
|
+
|
|
129
|
+
When a page is generated from an external source (e.g. docs-studio) and users
|
|
130
|
+
should not edit it in Confluence, add a `managedNotice`. It is injected into the
|
|
131
|
+
**storage** at publish time — never into the markdown source, so export and
|
|
132
|
+
round-trip stay clean, and git remains the single source of truth. The banner is
|
|
133
|
+
deterministic (fixed macro id), so it doesn't break the no-history-spam hash skip.
|
|
134
|
+
|
|
135
|
+
```ts
|
|
136
|
+
await publishPage({
|
|
137
|
+
pageId,
|
|
138
|
+
markdownPath,
|
|
139
|
+
managedNotice: {
|
|
140
|
+
linkUrl: 'https://studio.example/doc/123', // required
|
|
141
|
+
// everything below is optional:
|
|
142
|
+
linkText: 'постановка в docs-studio', // default: 'docs-studio'
|
|
143
|
+
text: 'Правьте страницу в {link}. Здесь только чтение — правки будут перезаписаны.',
|
|
144
|
+
panel: 'warning', // info | note | warning | tip (default: info)
|
|
145
|
+
position: 'top', // 'top' (default) | 'bottom'
|
|
146
|
+
},
|
|
147
|
+
}, cfg);
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
`{link}` in `text` is replaced by the link; omit it and the link is appended.
|
|
151
|
+
The presence of `managedNotice` turns the banner on — leave it out and no notice
|
|
152
|
+
is added. To apply one banner to every page of a CI plan, pass `managedNotice`
|
|
153
|
+
to `runPublish(dir, plan, { managedNotice })`; a per-page value overrides it.
|
|
154
|
+
|
|
124
155
|
## BPMN diagrams out of the box
|
|
125
156
|
|
|
126
157
|
Pass a `.bpmn` file as an image (a path or an `http(s)` URL) — it is
|
package/dist/index.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ export { Attachment, AttachmentService, toAttachmentVersion, SRC_SHA_SIDECAR_SUF
|
|
|
10
10
|
export { Page, Table } from './pages/page.js';
|
|
11
11
|
export { readTableFromConfluence, findTable, findTableInMacro, parseHtmlTable, decodeHtmlCell, renderMarkdownTable, escapeMdTableCell, readAndMapTable, type ColumnAlign, type TableColumn, } from './pages/tables.js';
|
|
12
12
|
export { publishPage, computeContentHash, DEFAULT_HASH_PROPERTY_KEY, type PublishPageOptions, type PublishPageResult, type TableData, } from './publish/publish.js';
|
|
13
|
+
export { buildManagedNotice, applyManagedNotice, DEFAULT_MANAGED_NOTICE_TEXT, DEFAULT_MANAGED_NOTICE_LINK_TEXT, type ManagedNoticeOptions, } from './publish/notice.js';
|
|
13
14
|
export { isHttpUrl, remoteFilename, isSameConfluenceOrigin, remoteRequestHeaders, downloadToFile, } from './publish/remote.js';
|
|
14
15
|
export { runPublish, type Here, type Build, type PublishPlan, type RunPublishOptions, } from './publish/runner.js';
|
|
15
16
|
export { parseStorage, serializeStorage, decodeEntities, StorageParseError, type XNode, type XElement, } from './export/xhtml.js';
|
package/dist/index.js
CHANGED
|
@@ -17,6 +17,7 @@ export { Page, Table } from './pages/page.js';
|
|
|
17
17
|
export { readTableFromConfluence, findTable, findTableInMacro, parseHtmlTable, decodeHtmlCell, renderMarkdownTable, escapeMdTableCell, readAndMapTable, } from './pages/tables.js';
|
|
18
18
|
// Publish
|
|
19
19
|
export { publishPage, computeContentHash, DEFAULT_HASH_PROPERTY_KEY, } from './publish/publish.js';
|
|
20
|
+
export { buildManagedNotice, applyManagedNotice, DEFAULT_MANAGED_NOTICE_TEXT, DEFAULT_MANAGED_NOTICE_LINK_TEXT, } from './publish/notice.js';
|
|
20
21
|
export { isHttpUrl, remoteFilename, isSameConfluenceOrigin, remoteRequestHeaders, downloadToFile, } from './publish/remote.js';
|
|
21
22
|
export { runPublish, } from './publish/runner.js';
|
|
22
23
|
// Export (storage → markdown) & round-trip
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* «Баннер управляемой страницы»: примечание о том, что страница ведётся во
|
|
3
|
+
* внешнем источнике (docs-studio), а правки в самой Confluence будут
|
|
4
|
+
* перезаписаны. Вставляется в storage при публикации, но НЕ в markdown-
|
|
5
|
+
* источник — round-trip/экспорт его не увидят, git остаётся чистым.
|
|
6
|
+
*/
|
|
7
|
+
export interface ManagedNoticeOptions {
|
|
8
|
+
/**
|
|
9
|
+
* URL источника (docs-studio / репозиторий постановки), куда ведёт ссылка.
|
|
10
|
+
* Обязателен — без него примечание не имеет смысла.
|
|
11
|
+
*/
|
|
12
|
+
linkUrl: string;
|
|
13
|
+
/** Текст ссылки. Default: {@link DEFAULT_MANAGED_NOTICE_LINK_TEXT}. */
|
|
14
|
+
linkText?: string;
|
|
15
|
+
/**
|
|
16
|
+
* Текст примечания. Плейсхолдер `{link}` заменяется ссылкой; если его в
|
|
17
|
+
* тексте нет, ссылка добавляется в конец. Default:
|
|
18
|
+
* {@link DEFAULT_MANAGED_NOTICE_TEXT}.
|
|
19
|
+
*/
|
|
20
|
+
text?: string;
|
|
21
|
+
/** Куда вставлять баннер: 'top' (шапка) или 'bottom' (низ). Default: 'top'. */
|
|
22
|
+
position?: 'top' | 'bottom';
|
|
23
|
+
/**
|
|
24
|
+
* Тип панели Confluence (влияет на цвет/иконку). Default: 'info'.
|
|
25
|
+
* 'warning' — красная, самый заметный вариант для «не редактировать».
|
|
26
|
+
*/
|
|
27
|
+
panel?: 'info' | 'note' | 'warning' | 'tip';
|
|
28
|
+
}
|
|
29
|
+
export declare const DEFAULT_MANAGED_NOTICE_TEXT: string;
|
|
30
|
+
export declare const DEFAULT_MANAGED_NOTICE_LINK_TEXT = "docs-studio";
|
|
31
|
+
/** Строит storage-разметку баннера (одиночный `<ac:structured-macro>`). */
|
|
32
|
+
export declare function buildManagedNotice(opts: ManagedNoticeOptions): string;
|
|
33
|
+
/** Дописывает баннер в начало (top) или конец (bottom) storage-контента. */
|
|
34
|
+
export declare function applyManagedNotice(storage: string, opts: ManagedNoticeOptions): string;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* «Баннер управляемой страницы»: примечание о том, что страница ведётся во
|
|
3
|
+
* внешнем источнике (docs-studio), а правки в самой Confluence будут
|
|
4
|
+
* перезаписаны. Вставляется в storage при публикации, но НЕ в markdown-
|
|
5
|
+
* источник — round-trip/экспорт его не увидят, git остаётся чистым.
|
|
6
|
+
*/
|
|
7
|
+
import { structuredMacro } from '../macros/xml.js';
|
|
8
|
+
import { escapeXmlAttr } from '../macros/xml.js';
|
|
9
|
+
export const DEFAULT_MANAGED_NOTICE_TEXT = 'Страница синхронизируется автоматически. Вносите правки в источнике ({link}) — ' +
|
|
10
|
+
'ручные изменения на этой странице будут перезаписаны при следующей публикации.';
|
|
11
|
+
export const DEFAULT_MANAGED_NOTICE_LINK_TEXT = 'docs-studio';
|
|
12
|
+
// Фиксированный ac:macro-id: баннер обязан давать БАЙТ-В-БАЙТ одинаковый
|
|
13
|
+
// storage при каждой публикации. Со случайным id (generateMacroId) content-
|
|
14
|
+
// hash менялся бы каждый раз → страница вечно считалась бы изменённой.
|
|
15
|
+
const NOTICE_MACRO_ID = '0f0e0d0c-0b0a-4009-8008-000000000001';
|
|
16
|
+
/** Собирает `<a href>`-ссылку на источник. */
|
|
17
|
+
function noticeLink(opts) {
|
|
18
|
+
const text = opts.linkText ?? DEFAULT_MANAGED_NOTICE_LINK_TEXT;
|
|
19
|
+
return `<a href="${escapeXmlAttr(opts.linkUrl)}">${escapeXmlAttr(text)}</a>`;
|
|
20
|
+
}
|
|
21
|
+
/** Строит storage-разметку баннера (одиночный `<ac:structured-macro>`). */
|
|
22
|
+
export function buildManagedNotice(opts) {
|
|
23
|
+
if (!opts.linkUrl)
|
|
24
|
+
throw new Error('managedNotice: linkUrl is required');
|
|
25
|
+
const panel = opts.panel ?? 'info';
|
|
26
|
+
const link = noticeLink(opts);
|
|
27
|
+
const text = opts.text ?? DEFAULT_MANAGED_NOTICE_TEXT;
|
|
28
|
+
const inner = text.includes('{link}')
|
|
29
|
+
? text.split('{link}').map(escapeXmlAttr).join(link)
|
|
30
|
+
: `${escapeXmlAttr(text)} ${link}`;
|
|
31
|
+
return structuredMacro(panel, NOTICE_MACRO_ID, { richBody: `<p>${inner}</p>` });
|
|
32
|
+
}
|
|
33
|
+
/** Дописывает баннер в начало (top) или конец (bottom) storage-контента. */
|
|
34
|
+
export function applyManagedNotice(storage, opts) {
|
|
35
|
+
const notice = buildManagedNotice(opts);
|
|
36
|
+
return (opts.position ?? 'top') === 'bottom' ? `${storage}${notice}` : `${notice}${storage}`;
|
|
37
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ConfluenceConfig } from '../client/config.js';
|
|
2
2
|
import { type RenderStorageOptions } from '../markdown/render.js';
|
|
3
|
+
import { type ManagedNoticeOptions } from './notice.js';
|
|
3
4
|
import type { MacroRegistry } from '../macros/registry.js';
|
|
4
5
|
import { Markdown } from '../markdown/markdown.js';
|
|
5
6
|
export interface TableData {
|
|
@@ -42,6 +43,13 @@ export interface PublishPageOptions {
|
|
|
42
43
|
labels?: string[];
|
|
43
44
|
/** Комментарий к версии страницы. */
|
|
44
45
|
versionMessage?: string;
|
|
46
|
+
/**
|
|
47
|
+
* Примечание «страница управляется извне» (docs-studio). Вставляется в
|
|
48
|
+
* storage при публикации, НО не в markdown-источник — экспорт/round-trip
|
|
49
|
+
* его не увидят. Наличие опции включает баннер; настраиваются текст, ссылка,
|
|
50
|
+
* её текст, тип панели и позиция (шапка/низ). См. {@link ManagedNoticeOptions}.
|
|
51
|
+
*/
|
|
52
|
+
managedNotice?: ManagedNoticeOptions;
|
|
45
53
|
/** Свой реестр макросов (default: встроенные core + table-filter). */
|
|
46
54
|
registry?: MacroRegistry;
|
|
47
55
|
/**
|
package/dist/publish/publish.js
CHANGED
|
@@ -8,6 +8,7 @@ import { bpmnOutputName, convertBpmn, isBpmnFile } from '../bpmn/convert.js';
|
|
|
8
8
|
import { downloadToFile, isHttpUrl, remoteFilename } from './remote.js';
|
|
9
9
|
import { renameImagePlaceholders, renderToStorage, } from '../markdown/render.js';
|
|
10
10
|
import { validateMarkdown } from '../markdown/validate.js';
|
|
11
|
+
import { applyManagedNotice } from './notice.js';
|
|
11
12
|
import { processMacros } from '../macros/registry.js';
|
|
12
13
|
import { defaultMacroRegistry } from '../macros/index.js';
|
|
13
14
|
import { Markdown } from '../markdown/markdown.js';
|
|
@@ -85,6 +86,10 @@ export async function publishPage(opts, cfg) {
|
|
|
85
86
|
const tables = opts.tables ?? [];
|
|
86
87
|
const registry = opts.registry ?? defaultMacroRegistry;
|
|
87
88
|
const hashKey = opts.hashPropertyKey ?? DEFAULT_HASH_PROPERTY_KEY;
|
|
89
|
+
// Fail fast: баннер без ссылки бессмыслен — падаем до любого сетевого I/O.
|
|
90
|
+
if (opts.managedNotice && !opts.managedNotice.linkUrl) {
|
|
91
|
+
throw new Error('publishPage: managedNotice.linkUrl is required');
|
|
92
|
+
}
|
|
88
93
|
// 0a. Удалённые источники: http(s)://-элементы в images[]/files[]
|
|
89
94
|
// заменяются на локальный путь в downloadDir с именем из URL — дальше
|
|
90
95
|
// конвейер (валидация, BPMN, аплоад) работает с обычными путями.
|
|
@@ -195,6 +200,13 @@ export async function publishPage(opts, cfg) {
|
|
|
195
200
|
let storage = renderToStorage(markdown, urls, opts.render ?? {});
|
|
196
201
|
// 2.5. Преобразование маркеров макросов в XHTML.
|
|
197
202
|
storage = processMacros(storage, registry).toString();
|
|
203
|
+
// 2.6. Баннер «страница управляется извне». Вставляется здесь, в storage —
|
|
204
|
+
// не в markdown — поэтому источник/round-trip его не содержат. Баннер
|
|
205
|
+
// детерминирован (фиксированный macro-id), значит попадает в content-hash
|
|
206
|
+
// стабильно и не ломает идемпотентность.
|
|
207
|
+
if (opts.managedNotice) {
|
|
208
|
+
storage = applyManagedNotice(storage, opts.managedNotice);
|
|
209
|
+
}
|
|
198
210
|
if (opts.dryRun) {
|
|
199
211
|
console.log(`[publish] dry-run: page ${pageId} rendered OK (${storage.length} bytes of storage)`);
|
|
200
212
|
return {
|
package/dist/publish/runner.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type PublishPageOptions, type PublishPageResult } from './publish.js';
|
|
2
|
+
import type { ManagedNoticeOptions } from './notice.js';
|
|
2
3
|
import { type ConfluenceConfig, type LoadConfigOptions } from '../client/config.js';
|
|
3
4
|
export type Here = (relativePath: string) => string;
|
|
4
5
|
export type Build = (relativePath: string) => string;
|
|
@@ -14,6 +15,12 @@ export interface RunPublishOptions {
|
|
|
14
15
|
* соответствует компоновке репо `<repo>/docs/<set>` + `<repo>/build/<set>`.
|
|
15
16
|
*/
|
|
16
17
|
buildDir?: string;
|
|
18
|
+
/**
|
|
19
|
+
* Баннер «страница управляется извне» по умолчанию для ВСЕХ страниц прогона
|
|
20
|
+
* (docs-studio). Значение, заданное у конкретной страницы в `managedNotice`,
|
|
21
|
+
* имеет приоритет. См. {@link ManagedNoticeOptions}.
|
|
22
|
+
*/
|
|
23
|
+
managedNotice?: ManagedNoticeOptions;
|
|
17
24
|
}
|
|
18
25
|
/**
|
|
19
26
|
* Минимальная точка входа для публикующего скрипта (`docs/<set>/publish.ts`).
|
package/dist/publish/runner.js
CHANGED
|
@@ -29,7 +29,11 @@ export async function runPublish(baseDir, plan, opts = {}) {
|
|
|
29
29
|
const pages = typeof plan === 'function' ? await plan(here, build) : plan;
|
|
30
30
|
const results = [];
|
|
31
31
|
for (const page of pages) {
|
|
32
|
-
|
|
32
|
+
// Дефолтный баннер прогона применяется, только если страница его не задала.
|
|
33
|
+
const merged = opts.managedNotice && page.managedNotice === undefined
|
|
34
|
+
? { ...page, managedNotice: opts.managedNotice }
|
|
35
|
+
: page;
|
|
36
|
+
results.push(await publishPage(merged, cfg));
|
|
33
37
|
}
|
|
34
38
|
return results;
|
|
35
39
|
}
|
package/package.json
CHANGED