@pterodoc/core 0.2.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/LICENCE.md +10 -0
- package/README.md +12 -0
- package/lib/chunks/capture-BhdvHwrN.js +428 -0
- package/lib/chunks/capture-BhdvHwrN.js.map +1 -0
- package/lib/chunks/issues-DBsPh6ek.js +55 -0
- package/lib/chunks/issues-DBsPh6ek.js.map +1 -0
- package/lib/chunks/mime-2SUyOolJ.js +62 -0
- package/lib/chunks/mime-2SUyOolJ.js.map +1 -0
- package/lib/chunks/paths-Bh6UsRI9.js +177 -0
- package/lib/chunks/paths-Bh6UsRI9.js.map +1 -0
- package/lib/config/load.d.ts +123 -0
- package/lib/config/load.d.ts.map +1 -0
- package/lib/config/types.d.ts +136 -0
- package/lib/config/types.d.ts.map +1 -0
- package/lib/errors.d.ts +53 -0
- package/lib/errors.d.ts.map +1 -0
- package/lib/index.d.ts +23 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +807 -0
- package/lib/index.js.map +1 -0
- package/lib/model/capture.d.ts +37 -0
- package/lib/model/capture.d.ts.map +1 -0
- package/lib/model/index.d.ts +13 -0
- package/lib/model/index.d.ts.map +1 -0
- package/lib/model/index.js +7 -0
- package/lib/model/index.js.map +1 -0
- package/lib/model/reader.d.ts +25 -0
- package/lib/model/reader.d.ts.map +1 -0
- package/lib/model/tree.d.ts +81 -0
- package/lib/model/tree.d.ts.map +1 -0
- package/lib/model/types.d.ts +166 -0
- package/lib/model/types.d.ts.map +1 -0
- package/lib/render/admonitions.d.ts +26 -0
- package/lib/render/admonitions.d.ts.map +1 -0
- package/lib/render/blocks.d.ts +39 -0
- package/lib/render/blocks.d.ts.map +1 -0
- package/lib/render/code.d.ts +37 -0
- package/lib/render/code.d.ts.map +1 -0
- package/lib/render/components.d.ts +49 -0
- package/lib/render/components.d.ts.map +1 -0
- package/lib/render/excerpt.d.ts +9 -0
- package/lib/render/excerpt.d.ts.map +1 -0
- package/lib/render/images.d.ts +58 -0
- package/lib/render/images.d.ts.map +1 -0
- package/lib/render/index.d.ts +80 -0
- package/lib/render/index.d.ts.map +1 -0
- package/lib/render/index.js +1336 -0
- package/lib/render/index.js.map +1 -0
- package/lib/render/inline.d.ts +25 -0
- package/lib/render/inline.d.ts.map +1 -0
- package/lib/render/links.d.ts +45 -0
- package/lib/render/links.d.ts.map +1 -0
- package/lib/render/mdx.d.ts +37 -0
- package/lib/render/mdx.d.ts.map +1 -0
- package/lib/render/page.d.ts +85 -0
- package/lib/render/page.d.ts.map +1 -0
- package/lib/render/parse.d.ts +27 -0
- package/lib/render/parse.d.ts.map +1 -0
- package/lib/render/references.d.ts +20 -0
- package/lib/render/references.d.ts.map +1 -0
- package/lib/render/renderers.d.ts +57 -0
- package/lib/render/renderers.d.ts.map +1 -0
- package/lib/render/slug.d.ts +34 -0
- package/lib/render/slug.d.ts.map +1 -0
- package/lib/render/theme.d.ts +62 -0
- package/lib/render/theme.d.ts.map +1 -0
- package/lib/sync/artifacts.d.ts +46 -0
- package/lib/sync/artifacts.d.ts.map +1 -0
- package/lib/sync/plan.d.ts +59 -0
- package/lib/sync/plan.d.ts.map +1 -0
- package/lib/sync/run.d.ts +39 -0
- package/lib/sync/run.d.ts.map +1 -0
- package/lib/target/index.d.ts +8 -0
- package/lib/target/index.d.ts.map +1 -0
- package/lib/target/index.js +2 -0
- package/lib/target/index.js.map +1 -0
- package/lib/target/target.d.ts +152 -0
- package/lib/target/target.d.ts.map +1 -0
- package/lib/util/hash.d.ts +10 -0
- package/lib/util/hash.d.ts.map +1 -0
- package/lib/util/index.d.ts +14 -0
- package/lib/util/index.d.ts.map +1 -0
- package/lib/util/index.js +7 -0
- package/lib/util/index.js.map +1 -0
- package/lib/util/issues.d.ts +52 -0
- package/lib/util/issues.d.ts.map +1 -0
- package/lib/util/mime.d.ts +16 -0
- package/lib/util/mime.d.ts.map +1 -0
- package/lib/util/paths.d.ts +42 -0
- package/lib/util/paths.d.ts.map +1 -0
- package/lib/version.d.ts +6 -0
- package/lib/version.d.ts.map +1 -0
- package/package.json +43 -0
- package/src/config/load.ts +387 -0
- package/src/config/types.ts +137 -0
- package/src/errors.ts +77 -0
- package/src/globals.d.ts +10 -0
- package/src/index.ts +31 -0
- package/src/model/capture.ts +92 -0
- package/src/model/index.ts +25 -0
- package/src/model/reader.ts +37 -0
- package/src/model/tree.ts +416 -0
- package/src/model/types.ts +167 -0
- package/src/render/admonitions.ts +74 -0
- package/src/render/blocks.ts +79 -0
- package/src/render/code.ts +112 -0
- package/src/render/components.ts +230 -0
- package/src/render/excerpt.ts +16 -0
- package/src/render/images.ts +113 -0
- package/src/render/index.ts +129 -0
- package/src/render/inline.ts +62 -0
- package/src/render/links.ts +94 -0
- package/src/render/mdx.ts +178 -0
- package/src/render/page.ts +289 -0
- package/src/render/parse.ts +65 -0
- package/src/render/references.ts +69 -0
- package/src/render/renderers.ts +346 -0
- package/src/render/slug.ts +63 -0
- package/src/render/theme.ts +92 -0
- package/src/sync/artifacts.ts +70 -0
- package/src/sync/plan.ts +69 -0
- package/src/sync/run.ts +641 -0
- package/src/target/index.ts +18 -0
- package/src/target/target.ts +146 -0
- package/src/util/hash.ts +14 -0
- package/src/util/index.ts +23 -0
- package/src/util/issues.ts +86 -0
- package/src/util/mime.ts +47 -0
- package/src/util/paths.ts +101 -0
- package/src/version.ts +31 -0
package/lib/index.js
ADDED
|
@@ -0,0 +1,807 @@
|
|
|
1
|
+
import { C as ConfigError, e as toSlugSegments, V as VERSION, t as titleCase } from './chunks/paths-Bh6UsRI9.js';
|
|
2
|
+
export { E as EXIT, P as PterodocError, T as TargetError, U as USER_AGENT, a as UnsupportedContentError } from './chunks/paths-Bh6UsRI9.js';
|
|
3
|
+
import fs, { readFileSync } from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { pathToFileURL } from 'node:url';
|
|
6
|
+
import dotenv from 'dotenv';
|
|
7
|
+
import { DEFAULT_LAYOUT, DEFAULT_STRINGS, createTheme, renderVersionBanner, collectImages, resolveImage, renderDoc, composePage, excerptFor } from './render/index.js';
|
|
8
|
+
import fs$1 from 'node:fs/promises';
|
|
9
|
+
import { I as IssueCollector } from './chunks/issues-DBsPh6ek.js';
|
|
10
|
+
export { c as compareSeverity, f as formatIssue } from './chunks/issues-DBsPh6ek.js';
|
|
11
|
+
import { c as contentHash, m as mimeTypeFor } from './chunks/mime-2SUyOolJ.js';
|
|
12
|
+
import { b as buildPageTree } from './chunks/capture-BhdvHwrN.js';
|
|
13
|
+
export { c as createCaptureReader, a as createMemoryReader, r as readCapture, s as serializeModel, w as writeCapture } from './chunks/capture-BhdvHwrN.js';
|
|
14
|
+
import 'gray-matter';
|
|
15
|
+
import 'unified';
|
|
16
|
+
import 'remark-parse';
|
|
17
|
+
import 'remark-gfm';
|
|
18
|
+
import 'remark-directive';
|
|
19
|
+
import 'remark-frontmatter';
|
|
20
|
+
import 'remark-mdx';
|
|
21
|
+
import 'unist-util-visit';
|
|
22
|
+
import 'mdast-util-to-hast';
|
|
23
|
+
import 'hast-util-to-html';
|
|
24
|
+
import 'github-slugger';
|
|
25
|
+
import 'mdast-util-to-string';
|
|
26
|
+
import 'node:crypto';
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* The configuration a site writes, and the resolved shape the tool reads.
|
|
30
|
+
*
|
|
31
|
+
* Credentials never appear here: the config names the environment variables
|
|
32
|
+
* that hold them, so a config file is safe to commit.
|
|
33
|
+
*/
|
|
34
|
+
/**
|
|
35
|
+
* Identity function that types a configuration file.
|
|
36
|
+
*
|
|
37
|
+
* @param config The configuration.
|
|
38
|
+
*/
|
|
39
|
+
function defineConfig(config) {
|
|
40
|
+
return config;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Discovering, merging and validating configuration.
|
|
45
|
+
*
|
|
46
|
+
* Precedence is flags, then the environment, then the config file, then the
|
|
47
|
+
* defaults. Nothing here touches `process.env` or the disk except through the
|
|
48
|
+
* dependencies it is given, so it is testable without either.
|
|
49
|
+
*/
|
|
50
|
+
/** File names tried, in order, when no config is named. */
|
|
51
|
+
const CONFIG_NAMES = [
|
|
52
|
+
'pterodoc.config.mjs',
|
|
53
|
+
'pterodoc.config.js',
|
|
54
|
+
'pterodoc.config.cjs',
|
|
55
|
+
'pterodoc.config.ts',
|
|
56
|
+
'pterodoc.config.json',
|
|
57
|
+
];
|
|
58
|
+
/** Find a configuration file next to the site. */
|
|
59
|
+
function discoverConfigFile(siteDir, deps) {
|
|
60
|
+
for (const name of CONFIG_NAMES) {
|
|
61
|
+
const candidate = path.join(siteDir, name);
|
|
62
|
+
if (deps.existsSync(candidate))
|
|
63
|
+
return candidate;
|
|
64
|
+
}
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
/** Load a configuration file. */
|
|
68
|
+
async function readConfigFile(file) {
|
|
69
|
+
if (file.endsWith('.json')) {
|
|
70
|
+
try {
|
|
71
|
+
return JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
throw new ConfigError(`${file} is not valid JSON: ${error.message}`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
try {
|
|
78
|
+
const module = (await import(pathToFileURL(file).href));
|
|
79
|
+
const config = module.default ?? module;
|
|
80
|
+
if (!config || typeof config !== 'object') {
|
|
81
|
+
throw new Error('the file exports no configuration object');
|
|
82
|
+
}
|
|
83
|
+
return config;
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
const message = error.message;
|
|
87
|
+
if (file.endsWith('.ts')) {
|
|
88
|
+
throw new ConfigError(`Could not load ${file}: ${message}\nA TypeScript config needs Node 22 or newer, which reads it directly. On an older Node, use pterodoc.config.mjs.`);
|
|
89
|
+
}
|
|
90
|
+
throw new ConfigError(`Could not load ${file}: ${message}`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/** One value, from the flags, the environment, the file, or the default. */
|
|
94
|
+
function pick(...candidates) {
|
|
95
|
+
for (const candidate of candidates) {
|
|
96
|
+
if (candidate !== undefined && candidate !== '')
|
|
97
|
+
return candidate;
|
|
98
|
+
}
|
|
99
|
+
return undefined;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Names that changed when this tool was extracted.
|
|
103
|
+
*
|
|
104
|
+
* The credential and URL variables kept their names, because they are what
|
|
105
|
+
* every existing setup already sets; only these two were renamed, and using
|
|
106
|
+
* one still works but says so.
|
|
107
|
+
*/
|
|
108
|
+
const RENAMED = {
|
|
109
|
+
WP_ROOT_PATH: 'PTERODOC_WP_ROOT',
|
|
110
|
+
WP_DOCS_BASE: 'PTERODOC_WP_BASE',
|
|
111
|
+
};
|
|
112
|
+
/**
|
|
113
|
+
* Read an environment value.
|
|
114
|
+
*
|
|
115
|
+
* `PTERODOC_`-prefixed names win; the plain names are equally supported except
|
|
116
|
+
* where one was renamed, which is reported.
|
|
117
|
+
*/
|
|
118
|
+
function fromEnv(env, name, aliases, notices) {
|
|
119
|
+
const prefixed = env[`PTERODOC_${name}`];
|
|
120
|
+
if (prefixed)
|
|
121
|
+
return prefixed;
|
|
122
|
+
for (const alias of aliases) {
|
|
123
|
+
const value = env[alias];
|
|
124
|
+
if (!value)
|
|
125
|
+
continue;
|
|
126
|
+
const current = RENAMED[alias];
|
|
127
|
+
if (current)
|
|
128
|
+
notices.push(`Using ${alias}; it is now called ${current}.`);
|
|
129
|
+
return value;
|
|
130
|
+
}
|
|
131
|
+
return undefined;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Resolve everything into the shape the run reads.
|
|
135
|
+
*
|
|
136
|
+
* @param input Flags, the file's contents, and the environment.
|
|
137
|
+
*/
|
|
138
|
+
function resolveConfig(input) {
|
|
139
|
+
const flags = input.flags ?? {};
|
|
140
|
+
const file = input.file ?? {};
|
|
141
|
+
const env = input.env ?? {};
|
|
142
|
+
const notices = [];
|
|
143
|
+
const site = file.site ?? {};
|
|
144
|
+
const target = file.target ?? {};
|
|
145
|
+
const render = file.render ?? {};
|
|
146
|
+
const media = file.media ?? {};
|
|
147
|
+
const output = file.output ?? {};
|
|
148
|
+
const baseDir = input.fileDir ?? process.cwd();
|
|
149
|
+
const siteDir = path.resolve(baseDir, pick(flags.siteDir, site.dir) ?? '.');
|
|
150
|
+
const userEnvName = target.auth?.userEnv ?? 'WP_USER';
|
|
151
|
+
const passwordEnvName = target.auth?.passwordEnv ?? 'WP_APP_PASSWORD';
|
|
152
|
+
const targetUrl = (pick(fromEnv(env, 'WP_URL', ['WP_URL'], notices), target.url) ?? '').replace(/\/+$/, '');
|
|
153
|
+
const user = (env[userEnvName] ?? '').trim();
|
|
154
|
+
const appPassword = (env[passwordEnvName] ?? '').replace(/\s+/g, '');
|
|
155
|
+
if (targetUrl && !/^https?:\/\//.test(targetUrl)) {
|
|
156
|
+
throw new ConfigError(`The target URL must start with http:// or https:// (got "${targetUrl}").`);
|
|
157
|
+
}
|
|
158
|
+
const missingCredentials = !targetUrl || !user || !appPassword;
|
|
159
|
+
const offline = flags.offline === true || missingCredentials;
|
|
160
|
+
if (missingCredentials && flags.offline !== true) {
|
|
161
|
+
notices.push(`No target URL or credentials (${userEnvName}, ${passwordEnvName}): running offline. Pages are rendered and nothing is sent.`);
|
|
162
|
+
}
|
|
163
|
+
const status = (pick(flags.status, fromEnv(env, 'WP_STATUS', ['WP_STATUS'], notices), target.status) ??
|
|
164
|
+
'publish');
|
|
165
|
+
if (status !== 'publish' && status !== 'draft' && status !== 'private') {
|
|
166
|
+
throw new ConfigError(`Status must be publish, draft or private (got "${status}").`);
|
|
167
|
+
}
|
|
168
|
+
const rootPath = pick(flags.root, fromEnv(env, 'WP_ROOT', ['WP_ROOT_PATH'], notices), target.root) ?? '/docs';
|
|
169
|
+
const basePath = flags.base !== undefined
|
|
170
|
+
? flags.base
|
|
171
|
+
: (pick(fromEnv(env, 'WP_BASE', ['WP_DOCS_BASE'], notices), target.base) ?? '');
|
|
172
|
+
const layout = { ...DEFAULT_LAYOUT, ...file.layout };
|
|
173
|
+
if (!['', 'wide', 'full'].includes(layout.align)) {
|
|
174
|
+
throw new ConfigError(`Layout alignment must be "", "wide" or "full" (got "${layout.align}").`);
|
|
175
|
+
}
|
|
176
|
+
const locales = flags.allLocales
|
|
177
|
+
? 'all'
|
|
178
|
+
: flags.locale && flags.locale.length > 0
|
|
179
|
+
? flags.locale
|
|
180
|
+
: (site.locales ?? 'default');
|
|
181
|
+
const versions = flags.allVersions
|
|
182
|
+
? 'all'
|
|
183
|
+
: flags.docsVersion && flags.docsVersion.length > 0
|
|
184
|
+
? flags.docsVersion
|
|
185
|
+
: (site.versions ?? 'last');
|
|
186
|
+
const outDir = path.resolve(baseDir, pick(flags.out, fromEnv(env, 'OUT', [], notices), output.dir) ?? '.pterodoc');
|
|
187
|
+
return {
|
|
188
|
+
siteDir,
|
|
189
|
+
configFile: input.configFile,
|
|
190
|
+
docusaurusConfig: pick(flags.docusaurusConfig, site.config),
|
|
191
|
+
modelFile: flags.model ? path.resolve(baseDir, flags.model) : undefined,
|
|
192
|
+
instances: flags.instance && flags.instance.length > 0 ? flags.instance : (site.instances ?? 'all'),
|
|
193
|
+
sidebars: site.sidebars ?? 'all',
|
|
194
|
+
versions,
|
|
195
|
+
locales,
|
|
196
|
+
includeDrafts: site.includeDrafts === true,
|
|
197
|
+
includeUnlisted: site.includeUnlisted === true,
|
|
198
|
+
includeOrphans: site.includeOrphans === true,
|
|
199
|
+
targetType: target.type ?? 'wordpress',
|
|
200
|
+
targetUrl,
|
|
201
|
+
user,
|
|
202
|
+
appPassword,
|
|
203
|
+
rootSegments: toSlugSegments(rootPath, 'the target root path'),
|
|
204
|
+
baseSegments: toSlugSegments(basePath, 'the target base'),
|
|
205
|
+
docsTitle: target.title ?? '',
|
|
206
|
+
status,
|
|
207
|
+
template: target.template ?? '',
|
|
208
|
+
lang: pick(fromEnv(env, 'WP_LANG', ['WP_LANG'], notices), target.lang) ?? '',
|
|
209
|
+
metaDescriptionKey: target.meta?.description ?? '',
|
|
210
|
+
methodOverride: fromEnv(env, 'METHOD_OVERRIDE', ['WP_METHOD_OVERRIDE'], notices) === '1' ||
|
|
211
|
+
target.methodOverride === true,
|
|
212
|
+
retry: {
|
|
213
|
+
attempts: target.retry?.attempts ?? 4,
|
|
214
|
+
baseDelayMs: target.retry?.baseDelayMs ?? 1000,
|
|
215
|
+
maxDelayMs: target.retry?.maxDelayMs ?? 30_000,
|
|
216
|
+
},
|
|
217
|
+
layout,
|
|
218
|
+
classPrefix: render.classPrefix ?? 'pterodoc',
|
|
219
|
+
blocks: render.blocks === 'plugin' ? 'plugin' : 'core',
|
|
220
|
+
dedupeTitle: render.dedupeTitle !== false,
|
|
221
|
+
unpublishedLinks: render.unpublishedLinks ?? 'site',
|
|
222
|
+
siteUrl: render.siteUrl ?? '',
|
|
223
|
+
excerptLength: render.excerptLength ?? 160,
|
|
224
|
+
strings: { ...DEFAULT_STRINGS, ...render.strings },
|
|
225
|
+
localeStrings: render.localeStrings ?? {},
|
|
226
|
+
mdxOnUnknown: file.mdx?.onUnknown ?? 'report',
|
|
227
|
+
uploadMedia: flags.noMedia === true ? false : media.upload !== false,
|
|
228
|
+
uploadRemoteMedia: media.uploadRemote === true,
|
|
229
|
+
mediaOnMissing: media.onMissing ?? 'warning',
|
|
230
|
+
mediaSlugPrefix: media.slugPrefix ?? 'pterodoc',
|
|
231
|
+
outDir,
|
|
232
|
+
writePages: output.pages !== false,
|
|
233
|
+
only: (flags.only ?? '').replace(/^\/+|\/+$/g, ''),
|
|
234
|
+
dryRun: flags.dryRun === true || offline,
|
|
235
|
+
prune: flags.prune === true,
|
|
236
|
+
offline,
|
|
237
|
+
strict: flags.strict === true,
|
|
238
|
+
strictAt: file.strict ?? 'error',
|
|
239
|
+
notices,
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Discover, read and resolve the configuration.
|
|
244
|
+
*
|
|
245
|
+
* @param flags Parsed command line flags.
|
|
246
|
+
* @param env The environment to read.
|
|
247
|
+
* @param deps Injected filesystem access.
|
|
248
|
+
*/
|
|
249
|
+
async function loadConfig(flags = {}, env = process.env, deps = {}) {
|
|
250
|
+
const existsSync = deps.existsSync ?? ((file) => fs.existsSync(file));
|
|
251
|
+
const cwd = deps.cwd ?? (() => process.cwd());
|
|
252
|
+
const startDir = path.resolve(cwd(), flags.siteDir ?? '.');
|
|
253
|
+
const configFile = flags.config
|
|
254
|
+
? path.resolve(cwd(), flags.config)
|
|
255
|
+
: discoverConfigFile(startDir, { existsSync });
|
|
256
|
+
if (flags.config && !existsSync(configFile)) {
|
|
257
|
+
throw new ConfigError(`No configuration file at ${configFile}.`);
|
|
258
|
+
}
|
|
259
|
+
const file = configFile ? await readConfigFile(configFile) : {};
|
|
260
|
+
const fileDir = configFile ? path.dirname(configFile) : startDir;
|
|
261
|
+
// A named env file is read only when asked for, so a developer's own .env
|
|
262
|
+
// can never leak into a test or a scripted run.
|
|
263
|
+
const envFile = flags.envFile ?? env['PTERODOC_ENV_FILE'];
|
|
264
|
+
const merged = envFile
|
|
265
|
+
? { ...dotenv.parse(fs.readFileSync(path.resolve(cwd(), envFile))), ...env }
|
|
266
|
+
: env;
|
|
267
|
+
return resolveConfig({ flags, file, fileDir, env: merged, configFile });
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/** What a run intends to do, and what it did. */
|
|
271
|
+
/** Count the actions by operation. */
|
|
272
|
+
function summarise(actions) {
|
|
273
|
+
const summary = {};
|
|
274
|
+
for (const action of actions)
|
|
275
|
+
summary[action.op] = (summary[action.op] ?? 0) + 1;
|
|
276
|
+
return summary;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* What a run leaves behind.
|
|
281
|
+
*
|
|
282
|
+
* The rendered pages and the plan are how a change is reviewed before it is
|
|
283
|
+
* published, and how a run that failed part way through is diagnosed.
|
|
284
|
+
*/
|
|
285
|
+
/** Where one page's rendered body is written. */
|
|
286
|
+
function pageFile(outDir, locale, versionName, treePath) {
|
|
287
|
+
const parts = [outDir, 'pages', locale, versionName];
|
|
288
|
+
const name = treePath === '' ? 'index.html' : `${treePath}.html`;
|
|
289
|
+
return path.join(...parts, name);
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Write the rendered pages, the manifest, the media map and the plan.
|
|
293
|
+
*
|
|
294
|
+
* Never called from a `finally`: a failure to write the artefacts must not
|
|
295
|
+
* replace the error that actually stopped the run.
|
|
296
|
+
*/
|
|
297
|
+
async function writeArtifacts(outDir, artifacts, options) {
|
|
298
|
+
await fs$1.mkdir(outDir, { recursive: true });
|
|
299
|
+
if (options.writePages) {
|
|
300
|
+
await fs$1.rm(path.join(outDir, 'pages'), { recursive: true, force: true });
|
|
301
|
+
for (const { page, locale, versionName } of artifacts.pages) {
|
|
302
|
+
const file = pageFile(outDir, locale, versionName, page.path);
|
|
303
|
+
await fs$1.mkdir(path.dirname(file), { recursive: true });
|
|
304
|
+
await fs$1.writeFile(file, page.content, 'utf8');
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
const write = async (name, value) => {
|
|
308
|
+
await fs$1.writeFile(path.join(outDir, name), `${JSON.stringify(value, null, 2)}\n`, 'utf8');
|
|
309
|
+
};
|
|
310
|
+
await write('manifest.json', artifacts.manifest);
|
|
311
|
+
await write('media.json', artifacts.media);
|
|
312
|
+
await write('plan.json', artifacts.plan);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* The reconciler.
|
|
317
|
+
*
|
|
318
|
+
* Two phases, because WordPress's navigation block needs a page id before any
|
|
319
|
+
* body can reference it: first make sure every page exists, then render every
|
|
320
|
+
* body against the real ids and write only what differs.
|
|
321
|
+
*/
|
|
322
|
+
/** Run the sync. */
|
|
323
|
+
async function runSync(config, deps) {
|
|
324
|
+
const log = deps.log ?? (() => { });
|
|
325
|
+
const issues = new IssueCollector();
|
|
326
|
+
const actions = [];
|
|
327
|
+
const prepared = [];
|
|
328
|
+
const mediaRecords = [];
|
|
329
|
+
let requests = 0;
|
|
330
|
+
let mediaPending = 0;
|
|
331
|
+
let docusaurusVersion = null;
|
|
332
|
+
const locales = await selectLocales(config, deps.reader);
|
|
333
|
+
for (const locale of locales) {
|
|
334
|
+
const model = await deps.reader.read(locale);
|
|
335
|
+
docusaurusVersion ??= model.docusaurusVersion;
|
|
336
|
+
const theme = createTheme({
|
|
337
|
+
classPrefix: config.classPrefix,
|
|
338
|
+
blocks: config.blocks,
|
|
339
|
+
strings: { ...config.strings, ...config.localeStrings[model.locale] },
|
|
340
|
+
});
|
|
341
|
+
const session = deps.target && !deps.renderOnly && !config.offline
|
|
342
|
+
? await deps.target.open({ locale: model.locale, dryRun: config.dryRun })
|
|
343
|
+
: undefined;
|
|
344
|
+
for (const instance of model.instances) {
|
|
345
|
+
for (const version of instance.versions) {
|
|
346
|
+
const result = await syncVersion({
|
|
347
|
+
config,
|
|
348
|
+
model,
|
|
349
|
+
instance: { admonitionKeywords: instance.admonitionKeywords },
|
|
350
|
+
version,
|
|
351
|
+
theme,
|
|
352
|
+
target: deps.target,
|
|
353
|
+
session,
|
|
354
|
+
issues,
|
|
355
|
+
log,
|
|
356
|
+
});
|
|
357
|
+
actions.push(...result.actions);
|
|
358
|
+
prepared.push(...result.prepared);
|
|
359
|
+
mediaRecords.push(...result.media);
|
|
360
|
+
mediaPending += result.mediaPending;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
if (session)
|
|
364
|
+
requests += session.requestCount();
|
|
365
|
+
}
|
|
366
|
+
const rootPath = deps.target?.rootPath ?? '/';
|
|
367
|
+
const plan = {
|
|
368
|
+
generatedAt: new Date().toISOString(),
|
|
369
|
+
versions: { pterodoc: VERSION, docusaurus: docusaurusVersion, node: process.version },
|
|
370
|
+
dryRun: config.dryRun,
|
|
371
|
+
offline: config.offline,
|
|
372
|
+
site: config.targetUrl || null,
|
|
373
|
+
rootPath,
|
|
374
|
+
locales,
|
|
375
|
+
versionNames: [...new Set(prepared.map((entry) => entry.versionName))],
|
|
376
|
+
actions,
|
|
377
|
+
issues: issues.issues,
|
|
378
|
+
summary: summarise(actions),
|
|
379
|
+
requests,
|
|
380
|
+
mediaPending,
|
|
381
|
+
artifactError: null,
|
|
382
|
+
};
|
|
383
|
+
const artifacts = {
|
|
384
|
+
pages: prepared.map(({ page, locale, versionName }) => ({ page, locale, versionName })),
|
|
385
|
+
manifest: prepared.map(({ node, page, locale, versionName }) => ({
|
|
386
|
+
path: page.path,
|
|
387
|
+
title: page.title,
|
|
388
|
+
slug: page.slug,
|
|
389
|
+
parent: node.parent ? node.parent.path : null,
|
|
390
|
+
menuOrder: page.menuOrder,
|
|
391
|
+
locale,
|
|
392
|
+
versionName,
|
|
393
|
+
file: page.file ?? null,
|
|
394
|
+
href: deps.target ? deps.target.hrefFor(page.path, { versionName, locale }) : page.path,
|
|
395
|
+
})),
|
|
396
|
+
media: mediaRecords,
|
|
397
|
+
plan,
|
|
398
|
+
};
|
|
399
|
+
try {
|
|
400
|
+
await writeArtifacts(config.outDir, artifacts, { writePages: config.writePages });
|
|
401
|
+
}
|
|
402
|
+
catch (error) {
|
|
403
|
+
plan.artifactError = error instanceof Error ? error.message : String(error);
|
|
404
|
+
}
|
|
405
|
+
return { plan, artifacts };
|
|
406
|
+
}
|
|
407
|
+
/** Which locales to publish. */
|
|
408
|
+
async function selectLocales(config, reader) {
|
|
409
|
+
if (Array.isArray(config.locales))
|
|
410
|
+
return config.locales;
|
|
411
|
+
if (config.locales === 'all')
|
|
412
|
+
return reader.locales();
|
|
413
|
+
const model = await reader.read();
|
|
414
|
+
return [model.defaultLocale ?? model.locale];
|
|
415
|
+
}
|
|
416
|
+
/** Publish one version of one docs instance. */
|
|
417
|
+
async function syncVersion(input) {
|
|
418
|
+
const { config, model, version, theme, target, session, issues, log } = input;
|
|
419
|
+
const actions = [];
|
|
420
|
+
const locale = model.locale;
|
|
421
|
+
const tree = buildPageTree({
|
|
422
|
+
version,
|
|
423
|
+
sidebars: config.sidebars,
|
|
424
|
+
rootTitle: config.docsTitle,
|
|
425
|
+
includeUnlisted: config.includeUnlisted,
|
|
426
|
+
includeOrphans: config.includeOrphans,
|
|
427
|
+
issues,
|
|
428
|
+
});
|
|
429
|
+
const context = { versionName: version.name, locale };
|
|
430
|
+
const href = (treePath) => target ? target.hrefFor(treePath, context) : `/${treePath}`;
|
|
431
|
+
// Phase 0 and 1: make sure the pages exist, so their ids are known.
|
|
432
|
+
const ids = new Map();
|
|
433
|
+
const created = new Set();
|
|
434
|
+
let rootParentId = null;
|
|
435
|
+
if (session) {
|
|
436
|
+
await session.loadIndex();
|
|
437
|
+
const root = await session.ensureRootParent();
|
|
438
|
+
rootParentId = root.id;
|
|
439
|
+
for (const entry of root.created) {
|
|
440
|
+
actions.push({ op: 'create-root', path: entry.path, id: entry.id, locale });
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
const inScope = (treePath) => !config.only || treePath === config.only || treePath.startsWith(`${config.only}/`);
|
|
444
|
+
const isNeeded = (treePath) => inScope(treePath) || treePath === '' || config.only.startsWith(`${treePath}/`);
|
|
445
|
+
const nodes = [tree.root, ...tree.chain];
|
|
446
|
+
for (const node of nodes) {
|
|
447
|
+
if (!isNeeded(node.path))
|
|
448
|
+
continue;
|
|
449
|
+
const parentId = node.parent ? (ids.get(node.parent.path) ?? null) : rootParentId;
|
|
450
|
+
if (!session) {
|
|
451
|
+
ids.set(node.path, null);
|
|
452
|
+
continue;
|
|
453
|
+
}
|
|
454
|
+
const result = await session.ensurePage({
|
|
455
|
+
path: node.path,
|
|
456
|
+
slug: node.slug,
|
|
457
|
+
parentId,
|
|
458
|
+
title: node.title,
|
|
459
|
+
menuOrder: node.menuOrder,
|
|
460
|
+
isRoot: node.path === '',
|
|
461
|
+
});
|
|
462
|
+
for (const warning of result.warnings) {
|
|
463
|
+
issues.add({ code: 'target-warning', severity: 'warning', message: warning, path: node.path });
|
|
464
|
+
}
|
|
465
|
+
ids.set(node.path, result.id);
|
|
466
|
+
if (result.created) {
|
|
467
|
+
created.add(node.path);
|
|
468
|
+
actions.push({
|
|
469
|
+
op: 'create',
|
|
470
|
+
path: node.path,
|
|
471
|
+
id: result.id,
|
|
472
|
+
locale,
|
|
473
|
+
versionName: version.name,
|
|
474
|
+
file: node.doc?.sourceRelativePath ?? null,
|
|
475
|
+
});
|
|
476
|
+
if (result.id !== null)
|
|
477
|
+
log(`created ${node.path || '(root)'} (id ${result.id})`);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
// Media: resolve every referenced file, upload what is new, and remember
|
|
481
|
+
// where each one ended up so the renderer can point at it.
|
|
482
|
+
const media = new Map();
|
|
483
|
+
const mediaRecords = [];
|
|
484
|
+
let mediaPending = 0;
|
|
485
|
+
if (config.uploadMedia) {
|
|
486
|
+
const result = await syncMedia({ config, model, version, tree, session, issues });
|
|
487
|
+
for (const [url, ref] of result.byUrl)
|
|
488
|
+
media.set(url, ref);
|
|
489
|
+
mediaRecords.push(...result.records);
|
|
490
|
+
mediaPending = result.pending;
|
|
491
|
+
actions.push(...result.actions);
|
|
492
|
+
}
|
|
493
|
+
// Phase 2: render against the real ids, and write what differs.
|
|
494
|
+
const navRootId = ids.get('') ?? null;
|
|
495
|
+
const prepared = [];
|
|
496
|
+
const banner = version.banner && !version.isLast
|
|
497
|
+
? renderVersionBanner(theme, version.label, version.banner)
|
|
498
|
+
: undefined;
|
|
499
|
+
for (const node of nodes) {
|
|
500
|
+
const page = renderPageFor({
|
|
501
|
+
node,
|
|
502
|
+
tree,
|
|
503
|
+
config,
|
|
504
|
+
model,
|
|
505
|
+
theme,
|
|
506
|
+
href,
|
|
507
|
+
navRootId,
|
|
508
|
+
admonitionKeywords: input.instance.admonitionKeywords,
|
|
509
|
+
media,
|
|
510
|
+
issues,
|
|
511
|
+
...(banner ? { banner } : {}),
|
|
512
|
+
});
|
|
513
|
+
prepared.push({ node, page, locale, versionName: version.name });
|
|
514
|
+
const id = ids.get(node.path);
|
|
515
|
+
if (!session || id === null || id === undefined)
|
|
516
|
+
continue;
|
|
517
|
+
if (!inScope(node.path) && !created.has(node.path))
|
|
518
|
+
continue;
|
|
519
|
+
const parentId = node.parent ? (ids.get(node.parent.path) ?? 0) : (rootParentId ?? 0);
|
|
520
|
+
const remote = await session.fetchPage(id);
|
|
521
|
+
const changed = session.diffPage(remote, page, parentId ?? 0);
|
|
522
|
+
if (changed.length === 0) {
|
|
523
|
+
actions.push({
|
|
524
|
+
op: 'unchanged',
|
|
525
|
+
path: node.path,
|
|
526
|
+
id,
|
|
527
|
+
locale,
|
|
528
|
+
versionName: version.name,
|
|
529
|
+
file: page.file ?? null,
|
|
530
|
+
});
|
|
531
|
+
continue;
|
|
532
|
+
}
|
|
533
|
+
actions.push({
|
|
534
|
+
op: 'update',
|
|
535
|
+
path: node.path,
|
|
536
|
+
id,
|
|
537
|
+
changed,
|
|
538
|
+
locale,
|
|
539
|
+
versionName: version.name,
|
|
540
|
+
file: page.file ?? null,
|
|
541
|
+
});
|
|
542
|
+
if (config.dryRun)
|
|
543
|
+
continue;
|
|
544
|
+
const { warnings } = await session.writePage(id, page, parentId ?? 0);
|
|
545
|
+
for (const warning of warnings) {
|
|
546
|
+
issues.add({ code: 'target-warning', severity: 'warning', message: warning, path: node.path });
|
|
547
|
+
}
|
|
548
|
+
log(`updated ${node.path || '(root)'} (${changed.join(', ')})`);
|
|
549
|
+
}
|
|
550
|
+
// Anything under this version's root that no document accounts for.
|
|
551
|
+
if (session && navRootId !== null) {
|
|
552
|
+
if (config.only) {
|
|
553
|
+
issues.add({
|
|
554
|
+
code: 'prune-skipped',
|
|
555
|
+
severity: 'info',
|
|
556
|
+
message: 'Pruning was skipped because --only limited the run.',
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
else {
|
|
560
|
+
const index = await session.loadIndex();
|
|
561
|
+
const keep = new Set();
|
|
562
|
+
for (const id of ids.values())
|
|
563
|
+
if (typeof id === 'number')
|
|
564
|
+
keep.add(id);
|
|
565
|
+
for (const page of session.computePrune(index, navRootId, keep)) {
|
|
566
|
+
actions.push({
|
|
567
|
+
op: 'prune',
|
|
568
|
+
path: page.slug,
|
|
569
|
+
id: page.id,
|
|
570
|
+
locale,
|
|
571
|
+
versionName: version.name,
|
|
572
|
+
applied: config.prune && !config.dryRun,
|
|
573
|
+
});
|
|
574
|
+
if (config.prune && !config.dryRun) {
|
|
575
|
+
await session.removePage(page);
|
|
576
|
+
log(`trashed ${page.link}`);
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
return { actions, prepared, media: mediaRecords, mediaPending };
|
|
582
|
+
}
|
|
583
|
+
/** Render one page, body and all. */
|
|
584
|
+
function renderPageFor(input) {
|
|
585
|
+
const { node, tree, config, model, theme, issues } = input;
|
|
586
|
+
const doc = node.doc;
|
|
587
|
+
let body = '';
|
|
588
|
+
let links = new Set();
|
|
589
|
+
let firstParagraph = '';
|
|
590
|
+
if (doc) {
|
|
591
|
+
const markdown = readDocument(doc, issues);
|
|
592
|
+
if (markdown !== undefined) {
|
|
593
|
+
const rendered = renderDoc({
|
|
594
|
+
markdown,
|
|
595
|
+
file: doc.sourceRelativePath,
|
|
596
|
+
permalink: doc.permalink,
|
|
597
|
+
format: doc.format,
|
|
598
|
+
theme,
|
|
599
|
+
admonitionKeywords: input.admonitionKeywords,
|
|
600
|
+
maintainCase: model.maintainCase,
|
|
601
|
+
dedupeTitle: config.dedupeTitle,
|
|
602
|
+
onUnknownJsx: config.mdxOnUnknown,
|
|
603
|
+
media: input.media,
|
|
604
|
+
issues,
|
|
605
|
+
resolveLink: makeLinkResolver(doc, tree, config, model, input.href),
|
|
606
|
+
});
|
|
607
|
+
body = rendered.body;
|
|
608
|
+
links = rendered.links;
|
|
609
|
+
firstParagraph = rendered.firstParagraph;
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
const content = composePage({
|
|
613
|
+
node,
|
|
614
|
+
body,
|
|
615
|
+
links,
|
|
616
|
+
href: input.href,
|
|
617
|
+
lookup: (treePath) => tree.byPath.get(treePath),
|
|
618
|
+
theme,
|
|
619
|
+
layout: config.layout,
|
|
620
|
+
navRootId: input.navRootId,
|
|
621
|
+
...(input.banner ? { banner: input.banner } : {}),
|
|
622
|
+
});
|
|
623
|
+
const excerpt = excerptFor(doc?.description ?? '', firstParagraph, config.excerptLength);
|
|
624
|
+
const meta = {};
|
|
625
|
+
if (config.metaDescriptionKey && excerpt)
|
|
626
|
+
meta[config.metaDescriptionKey] = excerpt;
|
|
627
|
+
return {
|
|
628
|
+
path: node.path,
|
|
629
|
+
slug: node.slug,
|
|
630
|
+
title: node.title,
|
|
631
|
+
content,
|
|
632
|
+
excerpt,
|
|
633
|
+
menuOrder: node.menuOrder,
|
|
634
|
+
meta,
|
|
635
|
+
file: doc?.sourceRelativePath,
|
|
636
|
+
versionName: node.versionName,
|
|
637
|
+
};
|
|
638
|
+
}
|
|
639
|
+
/** Read a document's source, reporting rather than throwing when it is gone. */
|
|
640
|
+
function readDocument(doc, issues) {
|
|
641
|
+
try {
|
|
642
|
+
return readFileSync(doc.sourceAbsolutePath, 'utf8');
|
|
643
|
+
}
|
|
644
|
+
catch (error) {
|
|
645
|
+
issues.addFor(doc, {
|
|
646
|
+
code: 'source-unreadable',
|
|
647
|
+
severity: 'error',
|
|
648
|
+
message: `Could not read ${doc.sourceRelativePath}: ${error.message}`,
|
|
649
|
+
});
|
|
650
|
+
return undefined;
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
/** Decide where each link in a document should point. */
|
|
654
|
+
function makeLinkResolver(doc, tree, config, model, href) {
|
|
655
|
+
const siteUrl = (config.siteUrl || model.url).replace(/\/+$/, '');
|
|
656
|
+
const baseUrl = model.baseUrl.replace(/\/+$/, '');
|
|
657
|
+
/**
|
|
658
|
+
* A root-relative markdown link is relative to the site, not to the server.
|
|
659
|
+
* Docusaurus's router prepends the base URL, so a link written as
|
|
660
|
+
* `/docs/api/` on a site served from `/project/` really means
|
|
661
|
+
* `/project/docs/api/`.
|
|
662
|
+
*/
|
|
663
|
+
const withBaseUrl = (urlPath) => !baseUrl || urlPath === baseUrl || urlPath.startsWith(`${baseUrl}/`)
|
|
664
|
+
? urlPath
|
|
665
|
+
: `${baseUrl}${urlPath}`;
|
|
666
|
+
return (rawHref) => {
|
|
667
|
+
if (!rawHref || rawHref.startsWith('#'))
|
|
668
|
+
return { href: rawHref };
|
|
669
|
+
if (/^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(rawHref) || rawHref.startsWith('//')) {
|
|
670
|
+
return { href: rawHref };
|
|
671
|
+
}
|
|
672
|
+
const hashIndex = rawHref.indexOf('#');
|
|
673
|
+
const hash = hashIndex === -1 ? '' : rawHref.slice(hashIndex);
|
|
674
|
+
const target = hashIndex === -1 ? rawHref : rawHref.slice(0, hashIndex);
|
|
675
|
+
const node = target.startsWith('/')
|
|
676
|
+
? findByUrl(tree, withBaseUrl(target))
|
|
677
|
+
: findBySourcePath(tree, doc, target);
|
|
678
|
+
if (node)
|
|
679
|
+
return { href: `${href(node.path)}${hash}`, path: node.path };
|
|
680
|
+
if (config.unpublishedLinks === 'drop')
|
|
681
|
+
return { href: null };
|
|
682
|
+
// Nothing here publishes it, so point at the documentation site, using the
|
|
683
|
+
// URL Docusaurus itself would serve.
|
|
684
|
+
const urlPath = target.startsWith('/')
|
|
685
|
+
? withBaseUrl(path.posix.normalize(target))
|
|
686
|
+
: path.posix.normalize(path.posix.join(doc.permalink, '..', target.replace(/\.mdx?$/, '')));
|
|
687
|
+
return { href: `${siteUrl}${urlPath}${hash}` };
|
|
688
|
+
};
|
|
689
|
+
}
|
|
690
|
+
/** A page whose document is served at this URL. */
|
|
691
|
+
function findByUrl(tree, target) {
|
|
692
|
+
const urlPath = path.posix.normalize(target).replace(/\.mdx?$/, '');
|
|
693
|
+
return tree.byPermalink.get(urlPath) ?? tree.byPermalink.get(`${urlPath}/`);
|
|
694
|
+
}
|
|
695
|
+
/**
|
|
696
|
+
* A page whose source file a relative link points at.
|
|
697
|
+
*
|
|
698
|
+
* Relative links are resolved against the file, which is how Docusaurus
|
|
699
|
+
* resolves them. Resolving against the URL instead would break every document
|
|
700
|
+
* whose front matter gives it a slug of its own.
|
|
701
|
+
*/
|
|
702
|
+
function findBySourcePath(tree, from, target) {
|
|
703
|
+
const base = path.posix.dirname(from.sourceRelativePath);
|
|
704
|
+
const resolved = path.posix.normalize(path.posix.join(base, target));
|
|
705
|
+
const withoutExtension = resolved.replace(/\.mdx?$/, '');
|
|
706
|
+
const candidates = [
|
|
707
|
+
resolved,
|
|
708
|
+
`${withoutExtension}.md`,
|
|
709
|
+
`${withoutExtension}.mdx`,
|
|
710
|
+
`${withoutExtension}/index.md`,
|
|
711
|
+
`${withoutExtension}/index.mdx`,
|
|
712
|
+
`${withoutExtension}/README.md`,
|
|
713
|
+
];
|
|
714
|
+
for (const candidate of candidates) {
|
|
715
|
+
const node = tree.bySourcePath.get(candidate);
|
|
716
|
+
if (node)
|
|
717
|
+
return node;
|
|
718
|
+
}
|
|
719
|
+
return undefined;
|
|
720
|
+
}
|
|
721
|
+
/** Resolve, upload and record every file the documents reference. */
|
|
722
|
+
async function syncMedia(input) {
|
|
723
|
+
const { config, model, version, tree, session, issues } = input;
|
|
724
|
+
const byUrl = new Map();
|
|
725
|
+
const records = [];
|
|
726
|
+
const actions = [];
|
|
727
|
+
let pending = 0;
|
|
728
|
+
const remote = session ? await session.loadMediaIndex() : new Map();
|
|
729
|
+
const uploadedThisRun = new Map();
|
|
730
|
+
for (const node of [tree.root, ...tree.chain]) {
|
|
731
|
+
const doc = node.doc;
|
|
732
|
+
if (!doc)
|
|
733
|
+
continue;
|
|
734
|
+
let markdown;
|
|
735
|
+
try {
|
|
736
|
+
markdown = await fs$1.readFile(doc.sourceAbsolutePath, 'utf8');
|
|
737
|
+
}
|
|
738
|
+
catch {
|
|
739
|
+
continue;
|
|
740
|
+
}
|
|
741
|
+
for (const image of collectImages(markdown, doc.format)) {
|
|
742
|
+
if (byUrl.has(image.url))
|
|
743
|
+
continue;
|
|
744
|
+
const resolved = resolveImage(image.url, {
|
|
745
|
+
sourceAbsolutePath: doc.sourceAbsolutePath,
|
|
746
|
+
contentPath: version.contentPath,
|
|
747
|
+
contentPathLocalized: version.contentPathLocalized,
|
|
748
|
+
staticDirs: model.staticDirs,
|
|
749
|
+
baseUrl: model.baseUrl,
|
|
750
|
+
siteDir: model.siteDir,
|
|
751
|
+
});
|
|
752
|
+
if (resolved.kind === 'external')
|
|
753
|
+
continue;
|
|
754
|
+
if (resolved.kind === 'missing') {
|
|
755
|
+
if (config.mediaOnMissing !== 'ignore') {
|
|
756
|
+
issues.addFor(doc, {
|
|
757
|
+
code: 'asset-missing',
|
|
758
|
+
severity: config.mediaOnMissing,
|
|
759
|
+
message: `The file "${image.url}" was not found, so the reference was left as written.`,
|
|
760
|
+
});
|
|
761
|
+
}
|
|
762
|
+
continue;
|
|
763
|
+
}
|
|
764
|
+
const bytes = await fs$1.readFile(resolved.file);
|
|
765
|
+
const hash = contentHash(bytes);
|
|
766
|
+
const known = uploadedThisRun.get(hash) ?? remote.get(hash);
|
|
767
|
+
if (known) {
|
|
768
|
+
byUrl.set(image.url, { id: known.id, url: known.url });
|
|
769
|
+
records.push({ hash, file: resolved.file, url: known.url, uploaded: false });
|
|
770
|
+
actions.push({ op: 'reuse-media', path: node.path, id: known.id, file: resolved.file });
|
|
771
|
+
continue;
|
|
772
|
+
}
|
|
773
|
+
if (!session || config.dryRun) {
|
|
774
|
+
pending += 1;
|
|
775
|
+
records.push({ hash, file: resolved.file, url: null, uploaded: false });
|
|
776
|
+
actions.push({ op: 'upload-media', path: node.path, id: null, file: resolved.file });
|
|
777
|
+
continue;
|
|
778
|
+
}
|
|
779
|
+
const filename = path.basename(resolved.file);
|
|
780
|
+
const mime = mimeTypeFor(filename);
|
|
781
|
+
if (!mime) {
|
|
782
|
+
issues.addFor(doc, {
|
|
783
|
+
code: 'asset-unknown-type',
|
|
784
|
+
severity: 'warning',
|
|
785
|
+
message: `Not uploading "${image.url}": its file type is not one WordPress accepts.`,
|
|
786
|
+
});
|
|
787
|
+
continue;
|
|
788
|
+
}
|
|
789
|
+
const uploaded = await session.uploadMedia({
|
|
790
|
+
bytes,
|
|
791
|
+
filename,
|
|
792
|
+
hash,
|
|
793
|
+
mime,
|
|
794
|
+
alt: image.alt,
|
|
795
|
+
title: image.title ?? titleCase(path.parse(filename).name),
|
|
796
|
+
});
|
|
797
|
+
uploadedThisRun.set(hash, uploaded);
|
|
798
|
+
byUrl.set(image.url, { id: uploaded.id, url: uploaded.url });
|
|
799
|
+
records.push({ hash, file: resolved.file, url: uploaded.url, uploaded: true });
|
|
800
|
+
actions.push({ op: 'upload-media', path: node.path, id: uploaded.id, file: resolved.file });
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
return { byUrl, records, actions, pending };
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
export { CONFIG_NAMES, ConfigError, DEFAULT_LAYOUT, IssueCollector, VERSION, buildPageTree, composePage, createTheme, defineConfig, discoverConfigFile, loadConfig, renderDoc, resolveConfig, runSync };
|
|
807
|
+
//# sourceMappingURL=index.js.map
|