@jacobbubu/md-to-lark 1.5.1 → 1.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 +5 -1
- package/dist/commands/publish-md/args.js +63 -1
- package/dist/commands/publish-md/command.js +35 -9
- package/dist/index.js +1 -0
- package/dist/pipeline/hast-to-last.js +375 -9
- package/dist/pipeline/index.js +1 -0
- package/dist/pipeline/markdown/md-to-semantic-hast.js +591 -0
- package/dist/protocol/capabilities.js +31 -0
- package/dist/protocol/contract.js +253 -0
- package/dist/protocol/frontmatter.js +43 -0
- package/dist/protocol/index.js +3 -0
- package/dist/protocol/types.js +1 -0
- package/dist/publish/asset-adapter.js +4 -1
- package/dist/publish/image-size-manifest.js +133 -0
- package/dist/publish/lark-tree-validator.js +31 -0
- package/dist/publish/process-file.js +178 -8
- package/dist/publish/render-report.js +45 -0
- package/dist/publish/runtime.js +4 -0
- package/dist/publish/stage-cache.js +29 -6
- package/package.json +4 -1
package/README.md
CHANGED
|
@@ -174,13 +174,17 @@ Guardrails:
|
|
|
174
174
|
- Single-file and recursive directory publish
|
|
175
175
|
- Title derivation, title prefix, and single-H1 promotion
|
|
176
176
|
- Local attachment and image detection with real upload
|
|
177
|
+
- Programmatic image display width control through `imageSizeResolver`
|
|
178
|
+
- Versioned `article-render/v1` contracts with inheritance, capability negotiation, strict pre-publish validation, and render reports
|
|
179
|
+
- Semantic figures, tables, equations, callouts, and GFM footnotes rendered to native Lark block structures
|
|
180
|
+
- Automatic `assets/manifest.yml` image sizing in renderer-contract mode
|
|
177
181
|
- Remote image download and standalone URL preparation
|
|
178
182
|
- Mermaid `text-drawing` and `board` output paths
|
|
179
183
|
- Opt-in single-dollar inline math parsing for LaTeX-heavy papers
|
|
180
184
|
- Table width heuristics and numeric-column right alignment
|
|
181
185
|
- Chinese Markdown formatting preset (`zh-format`)
|
|
182
186
|
- Ordered preset composition from CLI and programmatic usage
|
|
183
|
-
-
|
|
187
|
+
- Legacy stage cache output from `00-source` to `05-publish`; protocol mode adds contract and semantic stages through `07-publish`
|
|
184
188
|
- Programmatic access through `publishMdToLark`
|
|
185
189
|
|
|
186
190
|
## Where To Read Next
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
function usage() {
|
|
2
2
|
return [
|
|
3
|
-
'Usage: npm run publish:md -- --input <file.md|dir> [--
|
|
3
|
+
'Usage: npm run publish:md -- --input <file.md|dir> [--renderer lark] [--renderer-contract <path>] [--strict] [--render-report <path>] [--image-size-manifest <path>] [other options]',
|
|
4
4
|
'',
|
|
5
5
|
'Options:',
|
|
6
6
|
' --input Markdown file path, or directory path (publish all *.md recursively).',
|
|
@@ -10,6 +10,12 @@ function usage() {
|
|
|
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
12
|
' --resource-base-dir Base directory used to resolve relative local image/file paths. Default: the current markdown file directory.',
|
|
13
|
+
' --renderer Target renderer. Default: lark.',
|
|
14
|
+
' --renderer-contract Explicit article-render/v1 contract path.',
|
|
15
|
+
' --renderer-default-contract Fallback renderer contract path when no article contract is found.',
|
|
16
|
+
' --strict Require a valid renderer contract and fail before publish on semantic errors.',
|
|
17
|
+
' --render-report Write the article-render/v1 JSON report to this path.',
|
|
18
|
+
' --image-size-manifest Explicit assets/manifest.yml path for image display sizing.',
|
|
13
19
|
' --folder Feishu folder token. Default: LARK_FOLDER_TOKEN from .env',
|
|
14
20
|
' --doc Existing Feishu document id (single-file only). If set, publish directly into this doc (and clear content first).',
|
|
15
21
|
' --download-remote-images Enable prepare-stage remote image pre-download + link rewrite.',
|
|
@@ -58,6 +64,12 @@ export function parsePublishMdArgs(argv, env = process.env) {
|
|
|
58
64
|
const presetPaths = [];
|
|
59
65
|
let documentBaseUrl = '';
|
|
60
66
|
let resourceBaseDir = '';
|
|
67
|
+
let renderer = '';
|
|
68
|
+
let rendererContractPath = '';
|
|
69
|
+
let rendererDefaultContractPath = '';
|
|
70
|
+
let strict;
|
|
71
|
+
let renderReportPath = '';
|
|
72
|
+
let imageSizeManifestPath = '';
|
|
61
73
|
let folderToken = (env.LARK_FOLDER_TOKEN ?? '').trim();
|
|
62
74
|
let documentId;
|
|
63
75
|
let downloadRemoteImages;
|
|
@@ -137,6 +149,50 @@ export function parsePublishMdArgs(argv, env = process.env) {
|
|
|
137
149
|
i += 1;
|
|
138
150
|
continue;
|
|
139
151
|
}
|
|
152
|
+
if (arg === '--renderer') {
|
|
153
|
+
const value = argv[i + 1];
|
|
154
|
+
if (!value)
|
|
155
|
+
throw new Error('Missing value for --renderer.');
|
|
156
|
+
renderer = value;
|
|
157
|
+
i += 1;
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
if (arg === '--renderer-contract') {
|
|
161
|
+
const value = argv[i + 1];
|
|
162
|
+
if (!value)
|
|
163
|
+
throw new Error('Missing value for --renderer-contract.');
|
|
164
|
+
rendererContractPath = value;
|
|
165
|
+
i += 1;
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
if (arg === '--renderer-default-contract') {
|
|
169
|
+
const value = argv[i + 1];
|
|
170
|
+
if (!value)
|
|
171
|
+
throw new Error('Missing value for --renderer-default-contract.');
|
|
172
|
+
rendererDefaultContractPath = value;
|
|
173
|
+
i += 1;
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
if (arg === '--strict') {
|
|
177
|
+
strict = true;
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
if (arg === '--render-report') {
|
|
181
|
+
const value = argv[i + 1];
|
|
182
|
+
if (!value)
|
|
183
|
+
throw new Error('Missing value for --render-report.');
|
|
184
|
+
renderReportPath = value;
|
|
185
|
+
i += 1;
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
if (arg === '--image-size-manifest') {
|
|
189
|
+
const value = argv[i + 1];
|
|
190
|
+
if (!value)
|
|
191
|
+
throw new Error('Missing value for --image-size-manifest.');
|
|
192
|
+
imageSizeManifestPath = value;
|
|
193
|
+
i += 1;
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
140
196
|
if (arg === '--doc') {
|
|
141
197
|
const value = argv[i + 1];
|
|
142
198
|
if (!value)
|
|
@@ -245,6 +301,12 @@ export function parsePublishMdArgs(argv, env = process.env) {
|
|
|
245
301
|
: {}),
|
|
246
302
|
...(documentBaseUrl.trim() ? { documentBaseUrl: documentBaseUrl.trim() } : {}),
|
|
247
303
|
...(resourceBaseDir.trim() ? { resourceBaseDir: resourceBaseDir.trim() } : {}),
|
|
304
|
+
...(renderer.trim() ? { renderer: renderer.trim() } : {}),
|
|
305
|
+
...(rendererContractPath.trim() ? { rendererContractPath: rendererContractPath.trim() } : {}),
|
|
306
|
+
...(rendererDefaultContractPath.trim() ? { rendererDefaultContractPath: rendererDefaultContractPath.trim() } : {}),
|
|
307
|
+
...(strict === undefined ? {} : { strict }),
|
|
308
|
+
...(renderReportPath.trim() ? { renderReportPath: renderReportPath.trim() } : {}),
|
|
309
|
+
...(imageSizeManifestPath.trim() ? { imageSizeManifestPath: imageSizeManifestPath.trim() } : {}),
|
|
248
310
|
folderToken,
|
|
249
311
|
...(documentId ? { documentId: documentId.trim() } : {}),
|
|
250
312
|
...(downloadRemoteImages === undefined ? {} : { downloadRemoteImages }),
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { getPublishMdUsage, hasPublishMdHelpFlag, parsePublishMdArgs } from './args.js';
|
|
1
|
+
import { getPublishMdUsage, hasPublishMdHelpFlag, parsePublishMdArgs, } from './args.js';
|
|
2
2
|
import { resolvePublishInputSet } from './input-resolver.js';
|
|
3
3
|
import { loadMarkdownPresets } from './preset-loader.js';
|
|
4
4
|
import { createDocument, listFolderChildren, normalizeDocumentId } from '../../lark/docx/ops.js';
|
|
5
5
|
import { processSingleMarkdownFile } from '../../publish/process-file.js';
|
|
6
6
|
import { buildPublishRuntime, logPublishRuntimeSummary } from '../../publish/runtime.js';
|
|
7
7
|
import { sleep } from '../../shared/rate-limiter.js';
|
|
8
|
+
import { getRendererCapabilities, validateRendererContract } from '../../protocol/index.js';
|
|
8
9
|
export { getPublishMdUsage, parsePublishMdArgs };
|
|
9
10
|
function resolveMarkdownPresetRefs(options) {
|
|
10
11
|
if (options.presetPaths && options.presetPaths.length > 0) {
|
|
@@ -65,19 +66,29 @@ function createFolderDocumentResolver(runtime, options) {
|
|
|
65
66
|
};
|
|
66
67
|
}
|
|
67
68
|
export async function publishMdToLark(options, env = process.env) {
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
69
|
+
const publishOptions = {
|
|
70
|
+
...options,
|
|
71
|
+
folderToken: options.folderToken?.trim() || env.LARK_FOLDER_TOKEN?.trim() || '',
|
|
72
|
+
dryRun: options.dryRun ?? false,
|
|
73
|
+
};
|
|
74
|
+
if (!publishOptions.documentId && !publishOptions.folderToken) {
|
|
75
|
+
throw new Error('Folder token is required when documentId is not provided.');
|
|
76
|
+
}
|
|
77
|
+
const inputSet = await resolvePublishInputSet(publishOptions.inputPath);
|
|
78
|
+
const markdownPresets = await loadMarkdownPresets(resolveMarkdownPresetRefs(publishOptions));
|
|
79
|
+
if (publishOptions.documentId && inputSet.markdownFiles.length !== 1) {
|
|
71
80
|
throw new Error('--doc only supports single markdown input file.');
|
|
72
81
|
}
|
|
73
|
-
const runtime = buildPublishRuntime(
|
|
82
|
+
const runtime = buildPublishRuntime(publishOptions, env, markdownPresets);
|
|
74
83
|
logPublishRuntimeSummary(runtime, inputSet.markdownFiles.length, inputSet.mode);
|
|
75
|
-
const normalizedDocumentId =
|
|
76
|
-
const resolveTargetDocumentId =
|
|
84
|
+
const normalizedDocumentId = publishOptions.documentId ? normalizeDocumentId(publishOptions.documentId) : undefined;
|
|
85
|
+
const resolveTargetDocumentId = publishOptions.dryRun || normalizedDocumentId ? undefined : createFolderDocumentResolver(runtime, publishOptions);
|
|
77
86
|
const results = [];
|
|
78
87
|
for (let index = 0; index < inputSet.markdownFiles.length; index += 1) {
|
|
79
88
|
const markdownPath = inputSet.markdownFiles[index];
|
|
80
|
-
const perFileOptions = normalizedDocumentId
|
|
89
|
+
const perFileOptions = normalizedDocumentId
|
|
90
|
+
? { ...publishOptions, documentId: normalizedDocumentId }
|
|
91
|
+
: publishOptions;
|
|
81
92
|
const result = await processSingleMarkdownFile({
|
|
82
93
|
runtime,
|
|
83
94
|
inputSet,
|
|
@@ -92,7 +103,7 @@ export async function publishMdToLark(options, env = process.env) {
|
|
|
92
103
|
status: result.status,
|
|
93
104
|
documentUrl: result.documentUrl,
|
|
94
105
|
});
|
|
95
|
-
if (!
|
|
106
|
+
if (!publishOptions.dryRun && index < inputSet.markdownFiles.length - 1 && runtime.publishCooldownMs > 0) {
|
|
96
107
|
console.error(`[${index + 1}/${inputSet.markdownFiles.length}] Cooldown ${runtime.publishCooldownMs}ms before next markdown...`);
|
|
97
108
|
await sleep(runtime.publishCooldownMs);
|
|
98
109
|
}
|
|
@@ -104,6 +115,21 @@ export async function runPublishMdToLarkCli(argv, env = process.env) {
|
|
|
104
115
|
console.log(getPublishMdUsage());
|
|
105
116
|
return;
|
|
106
117
|
}
|
|
118
|
+
if (argv.includes('--print-capabilities')) {
|
|
119
|
+
const rendererIndex = argv.indexOf('--renderer');
|
|
120
|
+
const renderer = rendererIndex >= 0 ? argv[rendererIndex + 1] : undefined;
|
|
121
|
+
console.log(JSON.stringify(getRendererCapabilities(renderer), null, 2));
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
const validateIndex = argv.indexOf('--validate-contract');
|
|
125
|
+
if (validateIndex >= 0) {
|
|
126
|
+
const contractPath = argv[validateIndex + 1];
|
|
127
|
+
if (!contractPath)
|
|
128
|
+
throw new Error('Missing value for --validate-contract.');
|
|
129
|
+
const resolved = await validateRendererContract(contractPath);
|
|
130
|
+
console.log(JSON.stringify(resolved, null, 2));
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
107
133
|
const options = parsePublishMdArgs(argv, env);
|
|
108
134
|
const results = await publishMdToLark(options, env);
|
|
109
135
|
process.stdout.write(`${JSON.stringify(results, null, 2)}\n`);
|
package/dist/index.js
CHANGED