@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.
Files changed (130) hide show
  1. package/LICENCE.md +10 -0
  2. package/README.md +12 -0
  3. package/lib/chunks/capture-BhdvHwrN.js +428 -0
  4. package/lib/chunks/capture-BhdvHwrN.js.map +1 -0
  5. package/lib/chunks/issues-DBsPh6ek.js +55 -0
  6. package/lib/chunks/issues-DBsPh6ek.js.map +1 -0
  7. package/lib/chunks/mime-2SUyOolJ.js +62 -0
  8. package/lib/chunks/mime-2SUyOolJ.js.map +1 -0
  9. package/lib/chunks/paths-Bh6UsRI9.js +177 -0
  10. package/lib/chunks/paths-Bh6UsRI9.js.map +1 -0
  11. package/lib/config/load.d.ts +123 -0
  12. package/lib/config/load.d.ts.map +1 -0
  13. package/lib/config/types.d.ts +136 -0
  14. package/lib/config/types.d.ts.map +1 -0
  15. package/lib/errors.d.ts +53 -0
  16. package/lib/errors.d.ts.map +1 -0
  17. package/lib/index.d.ts +23 -0
  18. package/lib/index.d.ts.map +1 -0
  19. package/lib/index.js +807 -0
  20. package/lib/index.js.map +1 -0
  21. package/lib/model/capture.d.ts +37 -0
  22. package/lib/model/capture.d.ts.map +1 -0
  23. package/lib/model/index.d.ts +13 -0
  24. package/lib/model/index.d.ts.map +1 -0
  25. package/lib/model/index.js +7 -0
  26. package/lib/model/index.js.map +1 -0
  27. package/lib/model/reader.d.ts +25 -0
  28. package/lib/model/reader.d.ts.map +1 -0
  29. package/lib/model/tree.d.ts +81 -0
  30. package/lib/model/tree.d.ts.map +1 -0
  31. package/lib/model/types.d.ts +166 -0
  32. package/lib/model/types.d.ts.map +1 -0
  33. package/lib/render/admonitions.d.ts +26 -0
  34. package/lib/render/admonitions.d.ts.map +1 -0
  35. package/lib/render/blocks.d.ts +39 -0
  36. package/lib/render/blocks.d.ts.map +1 -0
  37. package/lib/render/code.d.ts +37 -0
  38. package/lib/render/code.d.ts.map +1 -0
  39. package/lib/render/components.d.ts +49 -0
  40. package/lib/render/components.d.ts.map +1 -0
  41. package/lib/render/excerpt.d.ts +9 -0
  42. package/lib/render/excerpt.d.ts.map +1 -0
  43. package/lib/render/images.d.ts +58 -0
  44. package/lib/render/images.d.ts.map +1 -0
  45. package/lib/render/index.d.ts +80 -0
  46. package/lib/render/index.d.ts.map +1 -0
  47. package/lib/render/index.js +1336 -0
  48. package/lib/render/index.js.map +1 -0
  49. package/lib/render/inline.d.ts +25 -0
  50. package/lib/render/inline.d.ts.map +1 -0
  51. package/lib/render/links.d.ts +45 -0
  52. package/lib/render/links.d.ts.map +1 -0
  53. package/lib/render/mdx.d.ts +37 -0
  54. package/lib/render/mdx.d.ts.map +1 -0
  55. package/lib/render/page.d.ts +85 -0
  56. package/lib/render/page.d.ts.map +1 -0
  57. package/lib/render/parse.d.ts +27 -0
  58. package/lib/render/parse.d.ts.map +1 -0
  59. package/lib/render/references.d.ts +20 -0
  60. package/lib/render/references.d.ts.map +1 -0
  61. package/lib/render/renderers.d.ts +57 -0
  62. package/lib/render/renderers.d.ts.map +1 -0
  63. package/lib/render/slug.d.ts +34 -0
  64. package/lib/render/slug.d.ts.map +1 -0
  65. package/lib/render/theme.d.ts +62 -0
  66. package/lib/render/theme.d.ts.map +1 -0
  67. package/lib/sync/artifacts.d.ts +46 -0
  68. package/lib/sync/artifacts.d.ts.map +1 -0
  69. package/lib/sync/plan.d.ts +59 -0
  70. package/lib/sync/plan.d.ts.map +1 -0
  71. package/lib/sync/run.d.ts +39 -0
  72. package/lib/sync/run.d.ts.map +1 -0
  73. package/lib/target/index.d.ts +8 -0
  74. package/lib/target/index.d.ts.map +1 -0
  75. package/lib/target/index.js +2 -0
  76. package/lib/target/index.js.map +1 -0
  77. package/lib/target/target.d.ts +152 -0
  78. package/lib/target/target.d.ts.map +1 -0
  79. package/lib/util/hash.d.ts +10 -0
  80. package/lib/util/hash.d.ts.map +1 -0
  81. package/lib/util/index.d.ts +14 -0
  82. package/lib/util/index.d.ts.map +1 -0
  83. package/lib/util/index.js +7 -0
  84. package/lib/util/index.js.map +1 -0
  85. package/lib/util/issues.d.ts +52 -0
  86. package/lib/util/issues.d.ts.map +1 -0
  87. package/lib/util/mime.d.ts +16 -0
  88. package/lib/util/mime.d.ts.map +1 -0
  89. package/lib/util/paths.d.ts +42 -0
  90. package/lib/util/paths.d.ts.map +1 -0
  91. package/lib/version.d.ts +6 -0
  92. package/lib/version.d.ts.map +1 -0
  93. package/package.json +43 -0
  94. package/src/config/load.ts +387 -0
  95. package/src/config/types.ts +137 -0
  96. package/src/errors.ts +77 -0
  97. package/src/globals.d.ts +10 -0
  98. package/src/index.ts +31 -0
  99. package/src/model/capture.ts +92 -0
  100. package/src/model/index.ts +25 -0
  101. package/src/model/reader.ts +37 -0
  102. package/src/model/tree.ts +416 -0
  103. package/src/model/types.ts +167 -0
  104. package/src/render/admonitions.ts +74 -0
  105. package/src/render/blocks.ts +79 -0
  106. package/src/render/code.ts +112 -0
  107. package/src/render/components.ts +230 -0
  108. package/src/render/excerpt.ts +16 -0
  109. package/src/render/images.ts +113 -0
  110. package/src/render/index.ts +129 -0
  111. package/src/render/inline.ts +62 -0
  112. package/src/render/links.ts +94 -0
  113. package/src/render/mdx.ts +178 -0
  114. package/src/render/page.ts +289 -0
  115. package/src/render/parse.ts +65 -0
  116. package/src/render/references.ts +69 -0
  117. package/src/render/renderers.ts +346 -0
  118. package/src/render/slug.ts +63 -0
  119. package/src/render/theme.ts +92 -0
  120. package/src/sync/artifacts.ts +70 -0
  121. package/src/sync/plan.ts +69 -0
  122. package/src/sync/run.ts +641 -0
  123. package/src/target/index.ts +18 -0
  124. package/src/target/target.ts +146 -0
  125. package/src/util/hash.ts +14 -0
  126. package/src/util/index.ts +23 -0
  127. package/src/util/issues.ts +86 -0
  128. package/src/util/mime.ts +47 -0
  129. package/src/util/paths.ts +101 -0
  130. package/src/version.ts +31 -0
@@ -0,0 +1,387 @@
1
+ /**
2
+ * Discovering, merging and validating configuration.
3
+ *
4
+ * Precedence is flags, then the environment, then the config file, then the
5
+ * defaults. Nothing here touches `process.env` or the disk except through the
6
+ * dependencies it is given, so it is testable without either.
7
+ */
8
+
9
+ import fs from 'node:fs';
10
+ import path from 'node:path';
11
+ import { pathToFileURL } from 'node:url';
12
+ import dotenv from 'dotenv';
13
+ import { ConfigError } from '../errors';
14
+ import { DEFAULT_LAYOUT, type PageLayout } from '../render/page';
15
+ import { DEFAULT_STRINGS, type Strings } from '../render/theme';
16
+ import type { Severity } from '../util/issues';
17
+ import { toSlugSegments } from '../util/paths';
18
+ import type { PterodocConfig } from './types';
19
+
20
+ /** File names tried, in order, when no config is named. */
21
+ export const CONFIG_NAMES = [
22
+ 'pterodoc.config.mjs',
23
+ 'pterodoc.config.js',
24
+ 'pterodoc.config.cjs',
25
+ 'pterodoc.config.ts',
26
+ 'pterodoc.config.json',
27
+ ];
28
+
29
+ /** Flags the CLI can supply, already parsed. */
30
+ export interface ConfigFlags {
31
+ siteDir?: string | undefined;
32
+ config?: string | undefined;
33
+ docusaurusConfig?: string | undefined;
34
+ model?: string | undefined;
35
+ instance?: string[] | undefined;
36
+ locale?: string[] | undefined;
37
+ allLocales?: boolean | undefined;
38
+ docsVersion?: string[] | undefined;
39
+ allVersions?: boolean | undefined;
40
+ root?: string | undefined;
41
+ base?: string | undefined;
42
+ status?: string | undefined;
43
+ only?: string | undefined;
44
+ out?: string | undefined;
45
+ dryRun?: boolean | undefined;
46
+ prune?: boolean | undefined;
47
+ offline?: boolean | undefined;
48
+ noMedia?: boolean | undefined;
49
+ strict?: boolean | undefined;
50
+ envFile?: string | undefined;
51
+ }
52
+
53
+ /** Everything the run needs, with nothing left to decide. */
54
+ export interface ResolvedConfig {
55
+ siteDir: string;
56
+ configFile: string | undefined;
57
+ docusaurusConfig: string | undefined;
58
+ modelFile: string | undefined;
59
+
60
+ instances: string[] | 'all';
61
+ sidebars: string[] | 'all';
62
+ versions: string[] | 'all' | 'last';
63
+ locales: string[] | 'all' | 'default';
64
+ includeDrafts: boolean;
65
+ includeUnlisted: boolean;
66
+ includeOrphans: boolean;
67
+
68
+ targetType: 'wordpress';
69
+ targetUrl: string;
70
+ user: string;
71
+ appPassword: string;
72
+ rootSegments: string[];
73
+ baseSegments: string[];
74
+ docsTitle: string;
75
+ status: 'publish' | 'draft' | 'private';
76
+ template: string;
77
+ lang: string;
78
+ metaDescriptionKey: string;
79
+ methodOverride: boolean;
80
+ retry: { attempts: number; baseDelayMs: number; maxDelayMs: number };
81
+
82
+ layout: PageLayout;
83
+ classPrefix: string;
84
+ /** Which block vocabulary the renderer emits. */
85
+ blocks: 'core' | 'plugin';
86
+ dedupeTitle: boolean;
87
+ unpublishedLinks: 'site' | 'drop';
88
+ siteUrl: string;
89
+ excerptLength: number;
90
+ strings: Strings;
91
+ localeStrings: Record<string, Partial<Strings>>;
92
+
93
+ mdxOnUnknown: 'report' | 'placeholder' | 'error';
94
+
95
+ uploadMedia: boolean;
96
+ uploadRemoteMedia: boolean;
97
+ mediaOnMissing: Severity | 'ignore';
98
+ mediaSlugPrefix: string;
99
+
100
+ outDir: string;
101
+ writePages: boolean;
102
+
103
+ only: string;
104
+ dryRun: boolean;
105
+ prune: boolean;
106
+ offline: boolean;
107
+ strict: boolean;
108
+ strictAt: Severity;
109
+
110
+ /** Told to the user before anything happens. */
111
+ notices: string[];
112
+ }
113
+
114
+ /** Injected so configuration can be resolved without touching the disk. */
115
+ export interface ConfigDeps {
116
+ readFileSync?: (file: string) => string;
117
+ existsSync?: (file: string) => boolean;
118
+ cwd?: () => string;
119
+ }
120
+
121
+ /** Find a configuration file next to the site. */
122
+ export function discoverConfigFile(
123
+ siteDir: string,
124
+ deps: Required<Pick<ConfigDeps, 'existsSync'>>,
125
+ ): string | undefined {
126
+ for (const name of CONFIG_NAMES) {
127
+ const candidate = path.join(siteDir, name);
128
+ if (deps.existsSync(candidate)) return candidate;
129
+ }
130
+ return undefined;
131
+ }
132
+
133
+ /** Load a configuration file. */
134
+ export async function readConfigFile(file: string): Promise<PterodocConfig> {
135
+ if (file.endsWith('.json')) {
136
+ try {
137
+ return JSON.parse(fs.readFileSync(file, 'utf8')) as PterodocConfig;
138
+ } catch (error) {
139
+ throw new ConfigError(`${file} is not valid JSON: ${(error as Error).message}`);
140
+ }
141
+ }
142
+ try {
143
+ const module = (await import(pathToFileURL(file).href)) as {
144
+ default?: PterodocConfig;
145
+ };
146
+ const config = module.default ?? (module as unknown as PterodocConfig);
147
+ if (!config || typeof config !== 'object') {
148
+ throw new Error('the file exports no configuration object');
149
+ }
150
+ return config;
151
+ } catch (error) {
152
+ const message = (error as Error).message;
153
+ if (file.endsWith('.ts')) {
154
+ throw new ConfigError(
155
+ `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.`,
156
+ );
157
+ }
158
+ throw new ConfigError(`Could not load ${file}: ${message}`);
159
+ }
160
+ }
161
+
162
+ /** One value, from the flags, the environment, the file, or the default. */
163
+ function pick<T>(...candidates: (T | undefined)[]): T | undefined {
164
+ for (const candidate of candidates) {
165
+ if (candidate !== undefined && candidate !== '') return candidate;
166
+ }
167
+ return undefined;
168
+ }
169
+
170
+ /**
171
+ * Names that changed when this tool was extracted.
172
+ *
173
+ * The credential and URL variables kept their names, because they are what
174
+ * every existing setup already sets; only these two were renamed, and using
175
+ * one still works but says so.
176
+ */
177
+ const RENAMED: Record<string, string> = {
178
+ WP_ROOT_PATH: 'PTERODOC_WP_ROOT',
179
+ WP_DOCS_BASE: 'PTERODOC_WP_BASE',
180
+ };
181
+
182
+ /**
183
+ * Read an environment value.
184
+ *
185
+ * `PTERODOC_`-prefixed names win; the plain names are equally supported except
186
+ * where one was renamed, which is reported.
187
+ */
188
+ function fromEnv(
189
+ env: NodeJS.ProcessEnv,
190
+ name: string,
191
+ aliases: string[],
192
+ notices: string[],
193
+ ): string | undefined {
194
+ const prefixed = env[`PTERODOC_${name}`];
195
+ if (prefixed) return prefixed;
196
+ for (const alias of aliases) {
197
+ const value = env[alias];
198
+ if (!value) continue;
199
+ const current = RENAMED[alias];
200
+ if (current) notices.push(`Using ${alias}; it is now called ${current}.`);
201
+ return value;
202
+ }
203
+ return undefined;
204
+ }
205
+
206
+ /**
207
+ * Resolve everything into the shape the run reads.
208
+ *
209
+ * @param input Flags, the file's contents, and the environment.
210
+ */
211
+ export function resolveConfig(input: {
212
+ flags?: ConfigFlags;
213
+ file?: PterodocConfig;
214
+ fileDir?: string;
215
+ env?: NodeJS.ProcessEnv;
216
+ configFile?: string | undefined;
217
+ }): ResolvedConfig {
218
+ const flags = input.flags ?? {};
219
+ const file = input.file ?? {};
220
+ const env = input.env ?? {};
221
+ const notices: string[] = [];
222
+
223
+ const site = file.site ?? {};
224
+ const target = file.target ?? {};
225
+ const render = file.render ?? {};
226
+ const media = file.media ?? {};
227
+ const output = file.output ?? {};
228
+
229
+ const baseDir = input.fileDir ?? process.cwd();
230
+ const siteDir = path.resolve(baseDir, pick(flags.siteDir, site.dir) ?? '.');
231
+
232
+ const userEnvName = target.auth?.userEnv ?? 'WP_USER';
233
+ const passwordEnvName = target.auth?.passwordEnv ?? 'WP_APP_PASSWORD';
234
+
235
+ const targetUrl = (
236
+ pick(fromEnv(env, 'WP_URL', ['WP_URL'], notices), target.url) ?? ''
237
+ ).replace(/\/+$/, '');
238
+ const user = (env[userEnvName] ?? '').trim();
239
+ const appPassword = (env[passwordEnvName] ?? '').replace(/\s+/g, '');
240
+
241
+ if (targetUrl && !/^https?:\/\//.test(targetUrl)) {
242
+ throw new ConfigError(`The target URL must start with http:// or https:// (got "${targetUrl}").`);
243
+ }
244
+
245
+ const missingCredentials = !targetUrl || !user || !appPassword;
246
+ const offline = flags.offline === true || missingCredentials;
247
+ if (missingCredentials && flags.offline !== true) {
248
+ notices.push(
249
+ `No target URL or credentials (${userEnvName}, ${passwordEnvName}): running offline. Pages are rendered and nothing is sent.`,
250
+ );
251
+ }
252
+
253
+ const status = (pick(flags.status, fromEnv(env, 'WP_STATUS', ['WP_STATUS'], notices), target.status) ??
254
+ 'publish') as ResolvedConfig['status'];
255
+ if (status !== 'publish' && status !== 'draft' && status !== 'private') {
256
+ throw new ConfigError(`Status must be publish, draft or private (got "${status}").`);
257
+ }
258
+
259
+ const rootPath = pick(flags.root, fromEnv(env, 'WP_ROOT', ['WP_ROOT_PATH'], notices), target.root) ?? '/docs';
260
+ const basePath =
261
+ flags.base !== undefined
262
+ ? flags.base
263
+ : (pick(fromEnv(env, 'WP_BASE', ['WP_DOCS_BASE'], notices), target.base) ?? '');
264
+
265
+ const layout: PageLayout = { ...DEFAULT_LAYOUT, ...file.layout };
266
+ if (!['', 'wide', 'full'].includes(layout.align)) {
267
+ throw new ConfigError(`Layout alignment must be "", "wide" or "full" (got "${layout.align}").`);
268
+ }
269
+
270
+ const locales: ResolvedConfig['locales'] = flags.allLocales
271
+ ? 'all'
272
+ : flags.locale && flags.locale.length > 0
273
+ ? flags.locale
274
+ : (site.locales ?? 'default');
275
+
276
+ const versions: ResolvedConfig['versions'] = flags.allVersions
277
+ ? 'all'
278
+ : flags.docsVersion && flags.docsVersion.length > 0
279
+ ? flags.docsVersion
280
+ : (site.versions ?? 'last');
281
+
282
+ const outDir = path.resolve(
283
+ baseDir,
284
+ pick(flags.out, fromEnv(env, 'OUT', [], notices), output.dir) ?? '.pterodoc',
285
+ );
286
+
287
+ return {
288
+ siteDir,
289
+ configFile: input.configFile,
290
+ docusaurusConfig: pick(flags.docusaurusConfig, site.config),
291
+ modelFile: flags.model ? path.resolve(baseDir, flags.model) : undefined,
292
+
293
+ instances: flags.instance && flags.instance.length > 0 ? flags.instance : (site.instances ?? 'all'),
294
+ sidebars: site.sidebars ?? 'all',
295
+ versions,
296
+ locales,
297
+ includeDrafts: site.includeDrafts === true,
298
+ includeUnlisted: site.includeUnlisted === true,
299
+ includeOrphans: site.includeOrphans === true,
300
+
301
+ targetType: target.type ?? 'wordpress',
302
+ targetUrl,
303
+ user,
304
+ appPassword,
305
+ rootSegments: toSlugSegments(rootPath, 'the target root path'),
306
+ baseSegments: toSlugSegments(basePath, 'the target base'),
307
+ docsTitle: target.title ?? '',
308
+ status,
309
+ template: target.template ?? '',
310
+ lang: pick(fromEnv(env, 'WP_LANG', ['WP_LANG'], notices), target.lang) ?? '',
311
+ metaDescriptionKey: target.meta?.description ?? '',
312
+ methodOverride:
313
+ fromEnv(env, 'METHOD_OVERRIDE', ['WP_METHOD_OVERRIDE'], notices) === '1' ||
314
+ target.methodOverride === true,
315
+ retry: {
316
+ attempts: target.retry?.attempts ?? 4,
317
+ baseDelayMs: target.retry?.baseDelayMs ?? 1000,
318
+ maxDelayMs: target.retry?.maxDelayMs ?? 30_000,
319
+ },
320
+
321
+ layout,
322
+ classPrefix: render.classPrefix ?? 'pterodoc',
323
+ blocks: render.blocks === 'plugin' ? 'plugin' : 'core',
324
+ dedupeTitle: render.dedupeTitle !== false,
325
+ unpublishedLinks: render.unpublishedLinks ?? 'site',
326
+ siteUrl: render.siteUrl ?? '',
327
+ excerptLength: render.excerptLength ?? 160,
328
+ strings: { ...DEFAULT_STRINGS, ...render.strings },
329
+ localeStrings: render.localeStrings ?? {},
330
+
331
+ mdxOnUnknown: file.mdx?.onUnknown ?? 'report',
332
+
333
+ uploadMedia: flags.noMedia === true ? false : media.upload !== false,
334
+ uploadRemoteMedia: media.uploadRemote === true,
335
+ mediaOnMissing: media.onMissing ?? 'warning',
336
+ mediaSlugPrefix: media.slugPrefix ?? 'pterodoc',
337
+
338
+ outDir,
339
+ writePages: output.pages !== false,
340
+
341
+ only: (flags.only ?? '').replace(/^\/+|\/+$/g, ''),
342
+ dryRun: flags.dryRun === true || offline,
343
+ prune: flags.prune === true,
344
+ offline,
345
+ strict: flags.strict === true,
346
+ strictAt: file.strict ?? 'error',
347
+
348
+ notices,
349
+ };
350
+ }
351
+
352
+ /**
353
+ * Discover, read and resolve the configuration.
354
+ *
355
+ * @param flags Parsed command line flags.
356
+ * @param env The environment to read.
357
+ * @param deps Injected filesystem access.
358
+ */
359
+ export async function loadConfig(
360
+ flags: ConfigFlags = {},
361
+ env: NodeJS.ProcessEnv = process.env,
362
+ deps: ConfigDeps = {},
363
+ ): Promise<ResolvedConfig> {
364
+ const existsSync = deps.existsSync ?? ((file: string) => fs.existsSync(file));
365
+ const cwd = deps.cwd ?? (() => process.cwd());
366
+
367
+ const startDir = path.resolve(cwd(), flags.siteDir ?? '.');
368
+ const configFile = flags.config
369
+ ? path.resolve(cwd(), flags.config)
370
+ : discoverConfigFile(startDir, { existsSync });
371
+
372
+ if (flags.config && !existsSync(configFile!)) {
373
+ throw new ConfigError(`No configuration file at ${configFile}.`);
374
+ }
375
+
376
+ const file = configFile ? await readConfigFile(configFile) : {};
377
+ const fileDir = configFile ? path.dirname(configFile) : startDir;
378
+
379
+ // A named env file is read only when asked for, so a developer's own .env
380
+ // can never leak into a test or a scripted run.
381
+ const envFile = flags.envFile ?? env['PTERODOC_ENV_FILE'];
382
+ const merged = envFile
383
+ ? { ...dotenv.parse(fs.readFileSync(path.resolve(cwd(), envFile))), ...env }
384
+ : env;
385
+
386
+ return resolveConfig({ flags, file, fileDir, env: merged, configFile });
387
+ }
@@ -0,0 +1,137 @@
1
+ /**
2
+ * The configuration a site writes, and the resolved shape the tool reads.
3
+ *
4
+ * Credentials never appear here: the config names the environment variables
5
+ * that hold them, so a config file is safe to commit.
6
+ */
7
+
8
+ import type { Severity } from '../util/issues';
9
+ import type { PageLayout } from '../render/page';
10
+ import type { Strings } from '../render/theme';
11
+
12
+ /** Which documents to publish. */
13
+ export interface SiteConfig {
14
+ /** Docusaurus site directory. Relative paths resolve against the config file. */
15
+ dir?: string;
16
+ /** Explicit `docusaurus.config.*` path. */
17
+ config?: string;
18
+ /** Docs plugin instances to publish, or 'all'. */
19
+ instances?: string[] | 'all';
20
+ /** Sidebars to publish. A document no kept sidebar reaches is not published. */
21
+ sidebars?: string[] | 'all';
22
+ /** Versions to publish: 'last', 'all', or explicit names. */
23
+ versions?: string[] | 'all' | 'last';
24
+ /** Locales to publish: 'default', 'all', or explicit codes. */
25
+ locales?: string[] | 'all' | 'default';
26
+ /** Publish documents Docusaurus marks as drafts. */
27
+ includeDrafts?: boolean;
28
+ /** Publish documents Docusaurus marks as unlisted. */
29
+ includeUnlisted?: boolean;
30
+ /** Publish documents that belong to no sidebar. */
31
+ includeOrphans?: boolean;
32
+ }
33
+
34
+ /** Where to publish. */
35
+ export interface TargetConfig {
36
+ /** The only implementation today. */
37
+ type?: 'wordpress';
38
+ /** Site origin. Usually supplied by the environment instead. */
39
+ url?: string;
40
+ /** Which environment variables hold the credentials. */
41
+ auth?: { userEnv?: string; passwordEnv?: string };
42
+ /** Path the documentation tree hangs from. */
43
+ root?: string;
44
+ /** Segment below the root holding the docs; '' publishes under the root. */
45
+ base?: string;
46
+ /** Title of the documentation root when no document claims it. */
47
+ title?: string;
48
+ /** Status applied to every synced page. */
49
+ status?: 'publish' | 'draft' | 'private';
50
+ /** Page template slug. */
51
+ template?: string;
52
+ /** Polylang language code. */
53
+ lang?: string;
54
+ /** Map a rendered field onto a target metadata key. */
55
+ meta?: { description?: string };
56
+ /** Send DELETE as POST with an override header. */
57
+ methodOverride?: boolean;
58
+ /** How hard to retry a busy site. */
59
+ retry?: { attempts?: number; baseDelayMs?: number; maxDelayMs?: number };
60
+ }
61
+
62
+ /** How pages are rendered. */
63
+ export interface RenderConfig {
64
+ /** Prefix on every generated class name. */
65
+ classPrefix?: string;
66
+ /**
67
+ * Which block vocabulary to emit.
68
+ *
69
+ * `core` is the default and emits core blocks only. `plugin` additionally
70
+ * carries instructions the pterodoc WordPress plugin understands — chiefly
71
+ * highlighted line ranges, which core blocks cannot express at all — in block
72
+ * comments rather than in markup, so WordPress stores the same content either
73
+ * way. Set it once the plugin is installed; `pterodoc doctor` says whether it
74
+ * is.
75
+ */
76
+ blocks?: 'core' | 'plugin';
77
+ /** Drop a leading H1 that repeats the page title. */
78
+ dedupeTitle?: boolean;
79
+ /** Where links to unpublished documents point: the site, or nowhere. */
80
+ unpublishedLinks?: 'site' | 'drop';
81
+ /** Overrides the documentation site URL used by `unpublishedLinks: 'site'`. */
82
+ siteUrl?: string;
83
+ /** Longest excerpt, in characters. */
84
+ excerptLength?: number;
85
+ /** Human strings, overriding the defaults. */
86
+ strings?: Partial<Strings>;
87
+ /** Per-locale string overrides. */
88
+ localeStrings?: Record<string, Partial<Strings>>;
89
+ }
90
+
91
+ /** How MDX is handled. */
92
+ export interface MdxConfig {
93
+ /** What to do about JSX with no translation. */
94
+ onUnknown?: 'report' | 'placeholder' | 'error';
95
+ }
96
+
97
+ /** How assets are handled. */
98
+ export interface MediaConfig {
99
+ /** Upload local files to the target. */
100
+ upload?: boolean;
101
+ /** Also upload files that already point at another origin. */
102
+ uploadRemote?: boolean;
103
+ /** What to do when a referenced file is missing. */
104
+ onMissing?: Severity | 'ignore';
105
+ /** Prefix of the media slug that carries the content hash. */
106
+ slugPrefix?: string;
107
+ }
108
+
109
+ /** Where the run writes what it did. */
110
+ export interface OutputConfig {
111
+ /** Directory for rendered pages, the manifest and the plan. */
112
+ dir?: string;
113
+ /** Write each rendered page body as a file. */
114
+ pages?: boolean;
115
+ }
116
+
117
+ /** A pterodoc configuration file. */
118
+ export interface PterodocConfig {
119
+ site?: SiteConfig;
120
+ target?: TargetConfig;
121
+ layout?: Partial<PageLayout>;
122
+ render?: RenderConfig;
123
+ mdx?: MdxConfig;
124
+ media?: MediaConfig;
125
+ output?: OutputConfig;
126
+ /** With `--strict`, an issue at this severity or above fails the run. */
127
+ strict?: Severity;
128
+ }
129
+
130
+ /**
131
+ * Identity function that types a configuration file.
132
+ *
133
+ * @param config The configuration.
134
+ */
135
+ export function defineConfig(config: PterodocConfig): PterodocConfig {
136
+ return config;
137
+ }
package/src/errors.ts ADDED
@@ -0,0 +1,77 @@
1
+ /**
2
+ * The error types the CLI knows how to present, and the exit codes they map to.
3
+ *
4
+ * Codes 0, 1 and 2 keep the meaning the DocStack sync script established, so a
5
+ * workflow that already branches on them does not change behaviour.
6
+ */
7
+
8
+ export const EXIT = {
9
+ /** Everything asked for was done. */
10
+ ok: 0,
11
+ /** The configuration or the command line was wrong; printed without a stack. */
12
+ config: 1,
13
+ /** The target refused a request. */
14
+ target: 2,
15
+ /** `--strict` was given and an issue reached the configured severity. */
16
+ strict: 3,
17
+ /** Something we did not anticipate; printed with a stack. */
18
+ internal: 4,
19
+ } as const;
20
+
21
+ /** Base class for every error this tool raises deliberately. */
22
+ export class PterodocError extends Error {
23
+ /** Exit code the CLI should use. */
24
+ readonly exitCode: number;
25
+
26
+ constructor(message: string, exitCode: number = EXIT.internal) {
27
+ super(message);
28
+ this.name = new.target.name;
29
+ this.exitCode = exitCode;
30
+ }
31
+ }
32
+
33
+ /** A user-fixable problem with the configuration, the flags or the environment. */
34
+ export class ConfigError extends PterodocError {
35
+ constructor(message: string) {
36
+ super(message, EXIT.config);
37
+ }
38
+ }
39
+
40
+ /** The target rejected a request, or could not be reached. */
41
+ export class TargetError extends PterodocError {
42
+ /** HTTP status, or 0 when the request never completed. */
43
+ readonly status: number;
44
+ /** Machine-readable code the target supplied, when it supplied one. */
45
+ readonly code: string | undefined;
46
+ /** HTTP method of the failed request. */
47
+ readonly method: string;
48
+ /** URL of the failed request. */
49
+ readonly url: string;
50
+ /** First part of the response body, for diagnosis. */
51
+ readonly bodySnippet: string;
52
+
53
+ constructor(
54
+ message: string,
55
+ details: {
56
+ status: number;
57
+ code?: string | undefined;
58
+ method: string;
59
+ url: string;
60
+ bodySnippet?: string;
61
+ },
62
+ ) {
63
+ super(message, EXIT.target);
64
+ this.status = details.status;
65
+ this.code = details.code;
66
+ this.method = details.method;
67
+ this.url = details.url;
68
+ this.bodySnippet = details.bodySnippet ?? '';
69
+ }
70
+ }
71
+
72
+ /** Content that this tool cannot represent in the target, when configured to fail on it. */
73
+ export class UnsupportedContentError extends PterodocError {
74
+ constructor(message: string) {
75
+ super(message, EXIT.strict);
76
+ }
77
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Ambient declarations for tokens the build substitutes.
3
+ *
4
+ * Kept in a declaration file so the replacement pass never sees the
5
+ * declaration itself: rewriting `declare const __PTERODOC_VERSION__` into
6
+ * `declare const "0.1.0"` is a syntax error.
7
+ */
8
+
9
+ /** The package's version, replaced at build time; absent when running from source. */
10
+ declare const __PTERODOC_VERSION__: string | undefined;
package/src/index.ts ADDED
@@ -0,0 +1,31 @@
1
+ /**
2
+ * The parts of pterodoc that know neither where a model came from nor where it
3
+ * is going: rendering, the site model, the target contract and the reconciler.
4
+ *
5
+ * Most consumers want the `pterodoc` package instead; this is what the source
6
+ * and target packages build on.
7
+ */
8
+
9
+ export { EXIT, PterodocError, ConfigError, TargetError, UnsupportedContentError } from './errors';
10
+ export { VERSION, USER_AGENT } from './version';
11
+ export { defineConfig } from './config/types';
12
+ export type {
13
+ PterodocConfig,
14
+ SiteConfig,
15
+ TargetConfig,
16
+ RenderConfig,
17
+ MdxConfig,
18
+ MediaConfig,
19
+ OutputConfig,
20
+ } from './config/types';
21
+ export { loadConfig, resolveConfig, discoverConfigFile, CONFIG_NAMES } from './config/load';
22
+ export type { ResolvedConfig, ConfigFlags } from './config/load';
23
+ export { runSync } from './sync/run';
24
+ export type { RunResult, RunSyncDeps } from './sync/run';
25
+ export type { Plan, Action } from './sync/plan';
26
+ export * from './model/index';
27
+ export * from './target/index';
28
+ export { renderDoc, createTheme, composePage, DEFAULT_LAYOUT } from './render/index';
29
+ export type { RenderedDoc, Theme, Strings, PageLayout } from './render/index';
30
+ export { IssueCollector, formatIssue, compareSeverity } from './util/issues';
31
+ export type { Issue, Severity } from './util/issues';