@jacobbubu/md-to-lark 1.3.1 → 1.4.1
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 +3 -0
- package/dist/commands/publish-md/args.js +14 -1
- package/dist/lark/docx/ops.js +6 -1
- package/dist/lark/docx/render-post-process.js +9 -2
- package/dist/last/image-defaults.js +9 -0
- package/dist/pipeline/hast-to-last.js +3 -6
- package/dist/publish/asset-adapter.js +3 -6
- package/dist/publish/process-file.js +3 -2
- package/dist/publish/runtime.js +5 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -86,6 +86,8 @@ Progress logs and exceptions are written to stderr.
|
|
|
86
86
|
- Otherwise use `LARK_DOCUMENT_BASE_URL`
|
|
87
87
|
- Otherwise fall back to the current compatibility derivation from `LARK_BASE_URL`
|
|
88
88
|
|
|
89
|
+
Relative local assets such as `./img-001.png` still resolve against the Markdown file directory by default. If your caller generates a temporary Markdown file elsewhere, use `--resource-base-dir` to keep local asset resolution pinned to the original content directory.
|
|
90
|
+
|
|
89
91
|
## Common Commands
|
|
90
92
|
|
|
91
93
|
Basic publish:
|
|
@@ -110,6 +112,7 @@ Presets, Mermaid, and stage artifacts:
|
|
|
110
112
|
npm run publish:md -- --input ./test-md/comp/comp.md --preset medium --dry-run
|
|
111
113
|
npm run publish:md -- --input ./test-md/comp/comp.md --preset zh-format --dry-run
|
|
112
114
|
npm run publish:md -- --input ./test-md/comp/comp.md --preset zh-format --preset ./my-preset.mjs --dry-run
|
|
115
|
+
npm run publish:md -- --input ./tmp/generated/article.md --resource-base-dir ./source-assets --dry-run
|
|
113
116
|
npm run publish:md -- --input ./test-md/mermaid.md --mermaid-target board --dry-run
|
|
114
117
|
npm run publish:md -- --input ./test-md/comp/comp.md --pipeline-cache-dir ./out/debug-cache --dry-run
|
|
115
118
|
```
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
function usage() {
|
|
2
2
|
return [
|
|
3
|
-
'Usage: npm run publish:md -- --input <file.md|dir> [--title <doc_title_or_prefix>] [--date-prefix|--no-date-prefix] [--preset <preset_name_or_module_path>]... [--document-base-url <base_url>] [--folder <folder_token>] [--doc <document_id>] [--download-remote-images|--no-download-remote-images] [--yt-dlp-path <path>] [--yt-dlp-cookies-path <path>] [--pipeline-cache-dir <dir>] [--mermaid-target <text-drawing|board>] [--mermaid-board-syntax-type <int>] [--mermaid-board-style-type <int>] [--mermaid-board-diagram-type <int>] [--dry-run] [--help|-h]',
|
|
3
|
+
'Usage: npm run publish:md -- --input <file.md|dir> [--title <doc_title_or_prefix>] [--date-prefix|--no-date-prefix] [--preset <preset_name_or_module_path>]... [--document-base-url <base_url>] [--resource-base-dir <dir>] [--folder <folder_token>] [--doc <document_id>] [--download-remote-images|--no-download-remote-images] [--yt-dlp-path <path>] [--yt-dlp-cookies-path <path>] [--pipeline-cache-dir <dir>] [--mermaid-target <text-drawing|board>] [--mermaid-board-syntax-type <int>] [--mermaid-board-style-type <int>] [--mermaid-board-diagram-type <int>] [--dry-run] [--help|-h]',
|
|
4
4
|
'',
|
|
5
5
|
'Options:',
|
|
6
6
|
' --input Markdown file path, or directory path (publish all *.md recursively).',
|
|
@@ -9,6 +9,7 @@ function usage() {
|
|
|
9
9
|
' --no-date-prefix Disable date prefix in final title.',
|
|
10
10
|
' --preset Optional preset module path (js/mjs/cjs/ts) or built-in name (e.g. medium). Repeatable; presets run in the given order before publish pipeline.',
|
|
11
11
|
' --document-base-url Base URL used to build documentUrl results (for example https://li.feishu.cn).',
|
|
12
|
+
' --resource-base-dir Base directory used to resolve relative local image/file paths. Default: the current markdown file directory.',
|
|
12
13
|
' --folder Feishu folder token. Default: LARK_FOLDER_TOKEN from .env',
|
|
13
14
|
' --doc Existing Feishu document id (single-file only). If set, publish directly into this doc (and clear content first).',
|
|
14
15
|
' --download-remote-images Enable prepare-stage remote image pre-download + link rewrite.',
|
|
@@ -33,12 +34,14 @@ function usage() {
|
|
|
33
34
|
' 7) Prepare stage can pre-download remote markdown images and optional yt-dlp URL lines.',
|
|
34
35
|
' 8) Leading YAML/TOML frontmatter is rewritten as fenced code block (yaml/toml), so it stays visible and will not be parsed as headings.',
|
|
35
36
|
' 9) Missing local asset files are skipped/degraded to text fallback; publish will not fail only because a referenced local path is absent.',
|
|
37
|
+
' 10) Relative local asset paths resolve against the markdown file directory by default; use --resource-base-dir to override that base.',
|
|
36
38
|
'',
|
|
37
39
|
'Examples:',
|
|
38
40
|
' npm run publish:md -- --input ./docs/a.md',
|
|
39
41
|
' npm run publish:md -- --input ./docs --title Weekly --folder <token>',
|
|
40
42
|
' npm run publish:md -- --input ./docs/a.md --doc <document_id>',
|
|
41
43
|
' npm run publish:md -- --input ./docs/a.md --dry-run',
|
|
44
|
+
' npm run publish:md -- --input ./tmp/generated/article.md --resource-base-dir ./original-assets --dry-run',
|
|
42
45
|
].join('\n');
|
|
43
46
|
}
|
|
44
47
|
export function getPublishMdUsage() {
|
|
@@ -53,6 +56,7 @@ export function parsePublishMdArgs(argv, env = process.env) {
|
|
|
53
56
|
let titleDatePrefix;
|
|
54
57
|
const presetPaths = [];
|
|
55
58
|
let documentBaseUrl = '';
|
|
59
|
+
let resourceBaseDir = '';
|
|
56
60
|
let folderToken = (env.LARK_FOLDER_TOKEN ?? '').trim();
|
|
57
61
|
let documentId;
|
|
58
62
|
let downloadRemoteImages;
|
|
@@ -123,6 +127,14 @@ export function parsePublishMdArgs(argv, env = process.env) {
|
|
|
123
127
|
i += 1;
|
|
124
128
|
continue;
|
|
125
129
|
}
|
|
130
|
+
if (arg === '--resource-base-dir') {
|
|
131
|
+
const value = argv[i + 1];
|
|
132
|
+
if (!value)
|
|
133
|
+
throw new Error('Missing value for --resource-base-dir.');
|
|
134
|
+
resourceBaseDir = value;
|
|
135
|
+
i += 1;
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
126
138
|
if (arg === '--doc') {
|
|
127
139
|
const value = argv[i + 1];
|
|
128
140
|
if (!value)
|
|
@@ -226,6 +238,7 @@ export function parsePublishMdArgs(argv, env = process.env) {
|
|
|
226
238
|
}
|
|
227
239
|
: {}),
|
|
228
240
|
...(documentBaseUrl.trim() ? { documentBaseUrl: documentBaseUrl.trim() } : {}),
|
|
241
|
+
...(resourceBaseDir.trim() ? { resourceBaseDir: resourceBaseDir.trim() } : {}),
|
|
229
242
|
folderToken,
|
|
230
243
|
...(documentId ? { documentId: documentId.trim() } : {}),
|
|
231
244
|
...(downloadRemoteImages === undefined ? {} : { downloadRemoteImages }),
|
package/dist/lark/docx/ops.js
CHANGED
|
@@ -511,7 +511,7 @@ export function isRelationMismatchError(error) {
|
|
|
511
511
|
return false;
|
|
512
512
|
return /1770013|relation mismatch/i.test(error.message);
|
|
513
513
|
}
|
|
514
|
-
export async function replaceImageBlock(client, documentId, blockId, imageToken, authOptions, limiter) {
|
|
514
|
+
export async function replaceImageBlock(client, documentId, blockId, imageToken, imageOptions, authOptions, limiter) {
|
|
515
515
|
await limiter.wait();
|
|
516
516
|
const response = await withRetry('docx.documentBlock.batchUpdate(replace_image)', async () => client.docx.documentBlock.batchUpdate({
|
|
517
517
|
path: {
|
|
@@ -523,6 +523,11 @@ export async function replaceImageBlock(client, documentId, blockId, imageToken,
|
|
|
523
523
|
block_id: blockId,
|
|
524
524
|
replace_image: {
|
|
525
525
|
token: imageToken,
|
|
526
|
+
...(typeof imageOptions.width === 'number' ? { width: imageOptions.width } : {}),
|
|
527
|
+
...(typeof imageOptions.height === 'number' ? { height: imageOptions.height } : {}),
|
|
528
|
+
...(typeof imageOptions.align === 'number' ? { align: imageOptions.align } : {}),
|
|
529
|
+
...(imageOptions.caption ? { caption: imageOptions.caption } : {}),
|
|
530
|
+
...(typeof imageOptions.scale === 'number' ? { scale: imageOptions.scale } : {}),
|
|
526
531
|
},
|
|
527
532
|
},
|
|
528
533
|
],
|
|
@@ -25,16 +25,23 @@ export async function applyCreatedImageBlock(client, documentId, createdBlockId,
|
|
|
25
25
|
const localPath = image && typeof image.local_path === 'string' ? image.local_path : '';
|
|
26
26
|
if (!localPath)
|
|
27
27
|
return null;
|
|
28
|
+
const replaceOptions = {
|
|
29
|
+
...(image && typeof image.width === 'number' ? { width: image.width } : {}),
|
|
30
|
+
...(image && typeof image.height === 'number' ? { height: image.height } : {}),
|
|
31
|
+
...(image && typeof image.align === 'number' ? { align: image.align } : {}),
|
|
32
|
+
...(image && toObjectRecord(image.caption) ? { caption: toObjectRecord(image.caption) } : {}),
|
|
33
|
+
...(image && typeof image.scale === 'number' ? { scale: image.scale } : {}),
|
|
34
|
+
};
|
|
28
35
|
let imageToken = await uploadBinaryToNode(client, 'docx_image', createdBlockId, localPath, authOptions, mediaLimiter);
|
|
29
36
|
try {
|
|
30
|
-
await replaceImageBlock(client, documentId, createdBlockId, imageToken, authOptions, docxLimiter);
|
|
37
|
+
await replaceImageBlock(client, documentId, createdBlockId, imageToken, replaceOptions, authOptions, docxLimiter);
|
|
31
38
|
}
|
|
32
39
|
catch (error) {
|
|
33
40
|
if (!isRelationMismatchError(error)) {
|
|
34
41
|
throw error;
|
|
35
42
|
}
|
|
36
43
|
imageToken = await uploadBinaryToNode(client, 'docx_image', createdBlockId, localPath, authOptions, mediaLimiter);
|
|
37
|
-
await replaceImageBlock(client, documentId, createdBlockId, imageToken, authOptions, docxLimiter);
|
|
44
|
+
await replaceImageBlock(client, documentId, createdBlockId, imageToken, replaceOptions, authOptions, docxLimiter);
|
|
38
45
|
}
|
|
39
46
|
return {
|
|
40
47
|
kind: 'image',
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export const DEFAULT_IMAGE_WIDTH = 1000;
|
|
2
|
+
export const DEFAULT_TABLE_CELL_IMAGE_WIDTH = 240;
|
|
3
|
+
export function createDefaultImagePayload(parentType) {
|
|
4
|
+
return {
|
|
5
|
+
width: parentType === 'table_cell' ? DEFAULT_TABLE_CELL_IMAGE_WIDTH : DEFAULT_IMAGE_WIDTH,
|
|
6
|
+
token: '',
|
|
7
|
+
align: 'left',
|
|
8
|
+
};
|
|
9
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { toString } from 'hast-util-to-string';
|
|
2
|
+
import { createDefaultImagePayload } from '../last/image-defaults.js';
|
|
2
3
|
import { LAST_TEXTUAL_BLOCK_TYPE_SET } from '../last/textual-block-types.js';
|
|
3
4
|
const BLOCK_CONTAINER_TAGS = new Set([
|
|
4
5
|
'article',
|
|
@@ -146,17 +147,13 @@ function createDividerBlock(ctx, parentId) {
|
|
|
146
147
|
}
|
|
147
148
|
function createImageBlock(ctx, parentId, sourceUrl) {
|
|
148
149
|
const blockId = nextBlockId(ctx);
|
|
150
|
+
const parentBlock = ctx.blocks[parentId];
|
|
149
151
|
const blockBase = {
|
|
150
152
|
id: blockId,
|
|
151
153
|
type: 'image',
|
|
152
154
|
parentId,
|
|
153
155
|
children: [],
|
|
154
|
-
payload:
|
|
155
|
-
width: 0,
|
|
156
|
-
height: 0,
|
|
157
|
-
token: '',
|
|
158
|
-
align: 'left',
|
|
159
|
-
},
|
|
156
|
+
payload: createDefaultImagePayload(parentBlock?.type),
|
|
160
157
|
};
|
|
161
158
|
if (sourceUrl) {
|
|
162
159
|
blockBase.selector = { attrs: { sourceUrl } };
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
+
import { createDefaultImagePayload } from '../last/image-defaults.js';
|
|
3
4
|
import { createDefaultMarks, extractTextFromInlines, firstInlineLinkUrl, getPathExtension, inferMediaKind, isTextualBlock, resolveLocalPathFromSource, shouldUsePreviewView, stripQueryAndHash, } from './common.js';
|
|
4
5
|
export function applyStandaloneAttachmentTransforms(last, baseDir) {
|
|
5
6
|
const localAssetByBlockId = new Map();
|
|
@@ -26,18 +27,14 @@ export function applyStandaloneAttachmentTransforms(last, baseDir) {
|
|
|
26
27
|
const displayName = extractTextFromInlines(block.payload.inlines).trim();
|
|
27
28
|
const fileName = displayName.length > 0 ? displayName : path.basename(stripQueryAndHash(linkUrl));
|
|
28
29
|
if (mediaKind === 'image') {
|
|
30
|
+
const parentBlock = block.parentId ? last.blocks[block.parentId] : undefined;
|
|
29
31
|
const transformed = {
|
|
30
32
|
id: block.id,
|
|
31
33
|
...(block.bttId ? { bttId: block.bttId } : {}),
|
|
32
34
|
type: 'image',
|
|
33
35
|
parentId: block.parentId,
|
|
34
36
|
children: [],
|
|
35
|
-
payload:
|
|
36
|
-
width: 0,
|
|
37
|
-
height: 0,
|
|
38
|
-
token: '',
|
|
39
|
-
align: 'left',
|
|
40
|
-
},
|
|
37
|
+
payload: createDefaultImagePayload(parentBlock?.type),
|
|
41
38
|
selector: {
|
|
42
39
|
attrs: {
|
|
43
40
|
sourceUrl: linkUrl,
|
|
@@ -69,6 +69,7 @@ export async function processSingleMarkdownFile(params) {
|
|
|
69
69
|
const stagePaths = buildPipelineStagePaths(runtime.pipelineCacheRootDir, markdownPath);
|
|
70
70
|
const startedAt = new Date().toISOString();
|
|
71
71
|
const sourceMarkdown = await readFile(markdownPath, 'utf8');
|
|
72
|
+
const resourceBaseDir = runtime.resourceBaseDir ?? path.dirname(markdownPath);
|
|
72
73
|
let markdown = sourceMarkdown;
|
|
73
74
|
for (let presetIndex = 0; presetIndex < runtime.markdownPresets.length; presetIndex += 1) {
|
|
74
75
|
const preset = runtime.markdownPresets[presetIndex];
|
|
@@ -82,6 +83,7 @@ export async function processSingleMarkdownFile(params) {
|
|
|
82
83
|
}
|
|
83
84
|
await writeSourceStage(stagePaths, sourceMarkdown, markdown, {
|
|
84
85
|
sourcePath: path.resolve(markdownPath),
|
|
86
|
+
resourceBaseDir,
|
|
85
87
|
preset: runtime.markdownPresets.length === 1 ? runtime.markdownPresets[0].displayPath : null,
|
|
86
88
|
presets: runtime.markdownPresets.map((preset) => preset.displayPath),
|
|
87
89
|
startedAt,
|
|
@@ -106,8 +108,7 @@ export async function processSingleMarkdownFile(params) {
|
|
|
106
108
|
});
|
|
107
109
|
await writeLastStage(stagePaths, last);
|
|
108
110
|
ensureLastBlockBttIds(last);
|
|
109
|
-
const
|
|
110
|
-
const localAssetByBlockId = applyStandaloneAttachmentTransforms(last, baseDir);
|
|
111
|
+
const localAssetByBlockId = applyStandaloneAttachmentTransforms(last, resourceBaseDir);
|
|
111
112
|
applyTableColumnWidthHeuristics(last);
|
|
112
113
|
const mermaidByBlockId = collectMermaidPatches(last);
|
|
113
114
|
const btt = convertLASTToBTT(last, {
|
package/dist/publish/runtime.js
CHANGED
|
@@ -77,6 +77,7 @@ export function buildPublishRuntime(options, env, markdownPresets) {
|
|
|
77
77
|
? env.LARK_DOCUMENT_BASE_URL.trim()
|
|
78
78
|
: deriveLarkDocumentBaseUrl(config.baseUrl);
|
|
79
79
|
const documentBaseUrl = normalizeLarkDocumentBaseUrl(documentBaseUrlCandidate);
|
|
80
|
+
const resourceBaseDir = options.resourceBaseDir?.trim() ? path.resolve(options.resourceBaseDir.trim()) : null;
|
|
80
81
|
const prepareTimeoutMs = toPositiveInt(Number((env.PREPARE_TIMEOUT_MS ?? '').trim())) ?? 15_000;
|
|
81
82
|
const prepareMaxRetries = toNonNegativeInt(Number((env.PREPARE_MAX_RETRIES ?? '').trim())) ?? 3;
|
|
82
83
|
const prepareBackoffBaseMs = toPositiveInt(Number((env.PREPARE_BACKOFF_BASE_MS ?? '').trim())) ?? 500;
|
|
@@ -103,6 +104,7 @@ export function buildPublishRuntime(options, env, markdownPresets) {
|
|
|
103
104
|
markdownPresets,
|
|
104
105
|
markdownPreset: markdownPresets[0] ?? null,
|
|
105
106
|
documentBaseUrl,
|
|
107
|
+
resourceBaseDir,
|
|
106
108
|
documentUrlFor: (documentId) => buildLarkDocumentUrl(documentBaseUrl, documentId),
|
|
107
109
|
authOptions,
|
|
108
110
|
sdkClient,
|
|
@@ -137,6 +139,9 @@ export function logPublishRuntimeSummary(runtime, inputCount, inputMode) {
|
|
|
137
139
|
? `Mermaid: target=board syntax_type=${String(runtime.mermaidRenderConfig.board.syntaxType)} style_type=${String(runtime.mermaidRenderConfig.board.styleType ?? '(default)')} diagram_type=${String(runtime.mermaidRenderConfig.board.diagramType ?? '(default)')}`
|
|
138
140
|
: 'Mermaid: target=text-drawing');
|
|
139
141
|
console.error(`Document URL base: ${runtime.documentBaseUrl}`);
|
|
142
|
+
if (runtime.resourceBaseDir) {
|
|
143
|
+
console.error(`Local asset base override: ${runtime.resourceBaseDir}`);
|
|
144
|
+
}
|
|
140
145
|
if (runtime.markdownPresets.length > 1) {
|
|
141
146
|
console.error(`Presets: ${runtime.markdownPresets.map((preset) => preset.displayPath).join(' -> ')}`);
|
|
142
147
|
return;
|